diff --git a/docs/configuration/data-validation.md b/docs/configuration/data-validation.md index 9ff71758f..1b8263de3 100644 --- a/docs/configuration/data-validation.md +++ b/docs/configuration/data-validation.md @@ -87,3 +87,24 @@ The following colors are supported: * `gray` * `black` * `white` + +--- + +## PROTECTION_RULES + +!!! tip "Dynamic Configuration Parameter" + +This is a mapping of models to [custom validators](../customization/custom-validation.md) against which an object is evaluated immediately prior to its deletion. If validation fails, the object is not deleted. An example is provided below: + +```python +PROTECTION_RULES = { + "dcim.site": [ + { + "status": { + "eq": "decommissioning" + } + }, + "my_plugin.validators.Validator1", + ] +} +``` diff --git a/docs/customization/custom-validation.md b/docs/customization/custom-validation.md index 30198117f..79aa82bc9 100644 --- a/docs/customization/custom-validation.md +++ b/docs/customization/custom-validation.md @@ -26,6 +26,8 @@ The `CustomValidator` class supports several validation types: * `regex`: Application of a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) * `required`: A value must be specified * `prohibited`: A value must _not_ be specified +* `eq`: A value must be equal to the specified value +* `neq`: A value must _not_ be equal to the specified value The `min` and `max` types should be defined for numeric values, whereas `min_length`, `max_length`, and `regex` are suitable for character strings (text values). The `required` and `prohibited` validators may be used for any field, and should be passed a value of `True`. diff --git a/docs/plugins/development/data-backends.md b/docs/plugins/development/data-backends.md new file mode 100644 index 000000000..feffa5bed --- /dev/null +++ b/docs/plugins/development/data-backends.md @@ -0,0 +1,23 @@ +# Data Backends + +[Data sources](../../models/core/datasource.md) can be defined to reference data which exists on systems of record outside NetBox, such as a git repository or Amazon S3 bucket. Plugins can register their own backend classes to introduce support for additional resource types. This is done by subclassing NetBox's `DataBackend` class. + +```python title="data_backends.py" +from netbox.data_backends import DataBackend + +class MyDataBackend(DataBackend): + name = 'mybackend' + label = 'My Backend' + ... +``` + +To register one or more data backends with NetBox, define a list named `backends` at the end of this file: + +```python title="data_backends.py" +backends = [MyDataBackend] +``` + +!!! tip + The path to the list of search indexes can be modified by setting `data_backends` in the PluginConfig instance. + +::: core.data_backends.DataBackend diff --git a/docs/plugins/development/index.md b/docs/plugins/development/index.md index dcbad9d8d..d3f50a0fb 100644 --- a/docs/plugins/development/index.md +++ b/docs/plugins/development/index.md @@ -109,6 +109,7 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i | `middleware` | A list of middleware classes to append after NetBox's build-in middleware | | `queues` | A list of custom background task queues to create | | `search_extensions` | The dotted path to the list of search index classes (default: `search.indexes`) | +| `data_backends` | The dotted path to the list of data source backend classes (default: `data_backends.backends`) | | `template_extensions` | The dotted path to the list of template extension classes (default: `template_content.template_extensions`) | | `menu_items` | The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`) | | `graphql_schema` | The dotted path to the plugin's GraphQL schema class, if any (default: `graphql.schema`) | diff --git a/mkdocs.yml b/mkdocs.yml index cc16434de..3e61f922a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -136,6 +136,7 @@ nav: - Forms: 'plugins/development/forms.md' - Filters & Filter Sets: 'plugins/development/filtersets.md' - Search: 'plugins/development/search.md' + - Data Backends: 'plugins/development/data-backends.md' - REST API: 'plugins/development/rest-api.md' - GraphQL API: 'plugins/development/graphql-api.md' - Background Tasks: 'plugins/development/background-tasks.md' diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index f4abda645..5223de339 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -85,7 +85,7 @@ class CircuitTypeSerializer(NetBoxModelSerializer): class Meta: model = CircuitType fields = [ - 'id', 'url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count', ] diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index e28238fea..4dd726803 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -137,7 +137,7 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet): class Meta: model = CircuitType - fields = ['id', 'name', 'slug', 'description'] + fields = ['id', 'name', 'slug', 'color', 'description'] class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet): @@ -154,12 +154,12 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte provider_account_id = django_filters.ModelMultipleChoiceFilter( field_name='provider_account', queryset=ProviderAccount.objects.all(), - label=_('ProviderAccount (ID)'), + label=_('Provider account (ID)'), ) provider_network_id = django_filters.ModelMultipleChoiceFilter( field_name='terminations__provider_network', queryset=ProviderNetwork.objects.all(), - label=_('ProviderNetwork (ID)'), + label=_('Provider network (ID)'), ) type_id = django_filters.ModelMultipleChoiceFilter( queryset=CircuitType.objects.all(), diff --git a/netbox/circuits/forms/bulk_edit.py b/netbox/circuits/forms/bulk_edit.py index 1a9366583..5c416bff9 100644 --- a/netbox/circuits/forms/bulk_edit.py +++ b/netbox/circuits/forms/bulk_edit.py @@ -7,7 +7,7 @@ from ipam.models import ASN from netbox.forms import NetBoxModelBulkEditForm from tenancy.models import Tenant from utilities.forms import add_blank_choice -from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField +from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField from utilities.forms.widgets import DatePicker, NumberWithOptions __all__ = ( @@ -91,6 +91,10 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm): class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm): + color = ColorField( + label=_('Color'), + required=False + ) description = forms.CharField( label=_('Description'), max_length=200, @@ -99,9 +103,9 @@ class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm): model = CircuitType fieldsets = ( - (None, ('description',)), + (None, ('color', 'description')), ) - nullable_fields = ('description',) + nullable_fields = ('color', 'description') class CircuitBulkEditForm(NetBoxModelBulkEditForm): diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py index d2217b45b..0c30e3cda 100644 --- a/netbox/circuits/forms/bulk_import.py +++ b/netbox/circuits/forms/bulk_import.py @@ -3,6 +3,7 @@ from django import forms from circuits.choices import CircuitStatusChoices from circuits.models import * from dcim.models import Site +from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant @@ -64,7 +65,10 @@ class CircuitTypeImportForm(NetBoxModelImportForm): class Meta: model = CircuitType - fields = ('name', 'slug', 'description', 'tags') + fields = ('name', 'slug', 'color', 'description', 'tags') + help_texts = { + 'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' 00ff00'), + } class CircuitImportForm(NetBoxModelImportForm): diff --git a/netbox/circuits/forms/filtersets.py b/netbox/circuits/forms/filtersets.py index 1fb239023..a82ec1726 100644 --- a/netbox/circuits/forms/filtersets.py +++ b/netbox/circuits/forms/filtersets.py @@ -7,7 +7,7 @@ from dcim.models import Region, Site, SiteGroup from ipam.models import ASN from netbox.forms import NetBoxModelFilterSetForm from tenancy.forms import TenancyFilterForm, ContactModelFilterForm -from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField +from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField from utilities.forms.widgets import DatePicker, NumberWithOptions __all__ = ( @@ -88,7 +88,7 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm): label=_('Provider') ) service_id = forms.CharField( - label=_('Service id'), + label=_('Service ID'), max_length=100, required=False ) @@ -97,8 +97,17 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm): class CircuitTypeFilterForm(NetBoxModelFilterSetForm): model = CircuitType + fieldsets = ( + (None, ('q', 'filter_id', 'tag')), + (_('Attributes'), ('color',)), + ) tag = TagFilterField(model) + color = ColorField( + label=_('Color'), + required=False + ) + class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Circuit diff --git a/netbox/circuits/forms/model_forms.py b/netbox/circuits/forms/model_forms.py index 8a540032e..0809cb2f4 100644 --- a/netbox/circuits/forms/model_forms.py +++ b/netbox/circuits/forms/model_forms.py @@ -76,14 +76,14 @@ class CircuitTypeForm(NetBoxModelForm): fieldsets = ( (_('Circuit Type'), ( - 'name', 'slug', 'description', 'tags', + 'name', 'slug', 'color', 'description', 'tags', )), ) class Meta: model = CircuitType fields = [ - 'name', 'slug', 'description', 'tags', + 'name', 'slug', 'color', 'description', 'tags', ] diff --git a/netbox/circuits/migrations/0043_circuittype_color.py b/netbox/circuits/migrations/0043_circuittype_color.py new file mode 100644 index 000000000..6c4dffeb6 --- /dev/null +++ b/netbox/circuits/migrations/0043_circuittype_color.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.5 on 2023-10-20 21:25 + +from django.db import migrations +import utilities.fields + + +class Migration(migrations.Migration): + dependencies = [ + ('circuits', '0042_provideraccount'), + ] + + operations = [ + migrations.AddField( + model_name='circuittype', + name='color', + field=utilities.fields.ColorField(blank=True, max_length=6), + ), + ] diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index 0322b67c6..4dc775364 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -7,6 +7,7 @@ from circuits.choices import * from dcim.models import CabledObjectModel from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ImageAttachmentsMixin, TagsMixin +from utilities.fields import ColorField __all__ = ( 'Circuit', @@ -20,6 +21,11 @@ class CircuitType(OrganizationalModel): Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named "Long Haul," "Metro," or "Out-of-Band". """ + color = ColorField( + verbose_name=_('color'), + blank=True + ) + def get_absolute_url(self): return reverse('circuits:circuittype', args=[self.pk]) diff --git a/netbox/circuits/tables/circuits.py b/netbox/circuits/tables/circuits.py index 6a05983e6..6ae727eca 100644 --- a/netbox/circuits/tables/circuits.py +++ b/netbox/circuits/tables/circuits.py @@ -28,6 +28,7 @@ class CircuitTypeTable(NetBoxTable): linkify=True, verbose_name=_('Name'), ) + color = columns.ColorColumn() tags = columns.TagColumn( url_name='circuits:circuittype_list' ) @@ -40,7 +41,7 @@ class CircuitTypeTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = CircuitType fields = ( - 'pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions', + 'pk', 'id', 'name', 'circuit_count', 'color', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions', ) default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug') diff --git a/netbox/core/api/serializers.py b/netbox/core/api/serializers.py index 4117a609c..4ae426df5 100644 --- a/netbox/core/api/serializers.py +++ b/netbox/core/api/serializers.py @@ -4,6 +4,7 @@ from core.choices import * from core.models import * from netbox.api.fields import ChoiceField, ContentTypeField from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer +from netbox.utils import get_data_backend_choices from users.api.nested_serializers import NestedUserSerializer from .nested_serializers import * @@ -19,7 +20,7 @@ class DataSourceSerializer(NetBoxModelSerializer): view_name='core-api:datasource-detail' ) type = ChoiceField( - choices=DataSourceTypeChoices + choices=get_data_backend_choices() ) status = ChoiceField( choices=DataSourceStatusChoices, @@ -68,5 +69,5 @@ class JobSerializer(BaseModelSerializer): model = Job fields = [ 'id', 'url', 'display', 'object_type', 'object_id', 'name', 'status', 'created', 'scheduled', 'interval', - 'started', 'completed', 'user', 'data', 'job_id', + 'started', 'completed', 'user', 'data', 'error', 'job_id', ] diff --git a/netbox/core/choices.py b/netbox/core/choices.py index b5d9d0d90..8d7050414 100644 --- a/netbox/core/choices.py +++ b/netbox/core/choices.py @@ -7,18 +7,6 @@ from utilities.choices import ChoiceSet # Data sources # -class DataSourceTypeChoices(ChoiceSet): - LOCAL = 'local' - GIT = 'git' - AMAZON_S3 = 'amazon-s3' - - CHOICES = ( - (LOCAL, _('Local'), 'gray'), - (GIT, 'Git', 'blue'), - (AMAZON_S3, 'Amazon S3', 'blue'), - ) - - class DataSourceStatusChoices(ChoiceSet): NEW = 'new' QUEUED = 'queued' diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 82b3962dd..9ff0b4d63 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -10,61 +10,24 @@ from django import forms from django.conf import settings from django.utils.translation import gettext as _ -from netbox.registry import registry -from .choices import DataSourceTypeChoices +from netbox.data_backends import DataBackend +from netbox.utils import register_data_backend from .exceptions import SyncError __all__ = ( - 'LocalBackend', 'GitBackend', + 'LocalBackend', 'S3Backend', ) logger = logging.getLogger('netbox.data_backends') -def register_backend(name): - """ - Decorator for registering a DataBackend class. - """ - - def _wrapper(cls): - registry['data_backends'][name] = cls - return cls - - return _wrapper - - -class DataBackend: - parameters = {} - sensitive_parameters = [] - - # Prevent Django's template engine from calling the backend - # class when referenced via DataSource.backend_class - do_not_call_in_templates = True - - def __init__(self, url, **kwargs): - self.url = url - self.params = kwargs - self.config = self.init_config() - - def init_config(self): - """ - Hook to initialize the instance's configuration. - """ - return - - @property - def url_scheme(self): - return urlparse(self.url).scheme.lower() - - @contextmanager - def fetch(self): - raise NotImplemented() - - -@register_backend(DataSourceTypeChoices.LOCAL) +@register_data_backend() class LocalBackend(DataBackend): + name = 'local' + label = _('Local') + is_local = True @contextmanager def fetch(self): @@ -74,8 +37,10 @@ class LocalBackend(DataBackend): yield local_path -@register_backend(DataSourceTypeChoices.GIT) +@register_data_backend() class GitBackend(DataBackend): + name = 'git' + label = 'Git' parameters = { 'username': forms.CharField( required=False, @@ -144,8 +109,10 @@ class GitBackend(DataBackend): local_path.cleanup() -@register_backend(DataSourceTypeChoices.AMAZON_S3) +@register_data_backend() class S3Backend(DataBackend): + name = 'amazon-s3' + label = 'Amazon S3' parameters = { 'aws_access_key_id': forms.CharField( label=_('AWS access key ID'), diff --git a/netbox/core/filtersets.py b/netbox/core/filtersets.py index 62a58086a..410e2e80c 100644 --- a/netbox/core/filtersets.py +++ b/netbox/core/filtersets.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext as _ import django_filters from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet +from netbox.utils import get_data_backend_choices from .choices import * from .models import * @@ -16,7 +17,7 @@ __all__ = ( class DataSourceFilterSet(NetBoxModelFilterSet): type = django_filters.MultipleChoiceFilter( - choices=DataSourceTypeChoices, + choices=get_data_backend_choices, null_value=None ) status = django_filters.MultipleChoiceFilter( diff --git a/netbox/core/forms/bulk_edit.py b/netbox/core/forms/bulk_edit.py index a4ecd646f..dcc92c6f0 100644 --- a/netbox/core/forms/bulk_edit.py +++ b/netbox/core/forms/bulk_edit.py @@ -1,10 +1,9 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from core.choices import DataSourceTypeChoices from core.models import * from netbox.forms import NetBoxModelBulkEditForm -from utilities.forms import add_blank_choice +from netbox.utils import get_data_backend_choices from utilities.forms.fields import CommentField from utilities.forms.widgets import BulkEditNullBooleanSelect @@ -16,9 +15,8 @@ __all__ = ( class DataSourceBulkEditForm(NetBoxModelBulkEditForm): type = forms.ChoiceField( label=_('Type'), - choices=add_blank_choice(DataSourceTypeChoices), - required=False, - initial='' + choices=get_data_backend_choices, + required=False ) enabled = forms.NullBooleanField( required=False, diff --git a/netbox/core/forms/filtersets.py b/netbox/core/forms/filtersets.py index f7a6f3595..4d0acbb77 100644 --- a/netbox/core/forms/filtersets.py +++ b/netbox/core/forms/filtersets.py @@ -8,6 +8,7 @@ from core.models import * from extras.forms.mixins import SavedFiltersMixin from extras.utils import FeatureQuery from netbox.forms import NetBoxModelFilterSetForm +from netbox.utils import get_data_backend_choices from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm from utilities.forms.fields import ContentTypeChoiceField, DynamicModelMultipleChoiceField from utilities.forms.widgets import APISelectMultiple, DateTimePicker @@ -27,7 +28,7 @@ class DataSourceFilterForm(NetBoxModelFilterSetForm): ) type = forms.MultipleChoiceField( label=_('Type'), - choices=DataSourceTypeChoices, + choices=get_data_backend_choices, required=False ) status = forms.MultipleChoiceField( diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index 01d5474c6..e3184acf6 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -7,6 +7,7 @@ from core.forms.mixins import SyncedDataMixin from core.models import * from netbox.forms import NetBoxModelForm from netbox.registry import registry +from netbox.utils import get_data_backend_choices from utilities.forms import get_field_value from utilities.forms.fields import CommentField from utilities.forms.widgets import HTMXSelect @@ -18,6 +19,10 @@ __all__ = ( class DataSourceForm(NetBoxModelForm): + type = forms.ChoiceField( + choices=get_data_backend_choices, + widget=HTMXSelect() + ) comments = CommentField() class Meta: @@ -26,7 +31,6 @@ class DataSourceForm(NetBoxModelForm): 'name', 'type', 'source_url', 'enabled', 'description', 'comments', 'ignore_rules', 'tags', ] widgets = { - 'type': HTMXSelect(), 'ignore_rules': forms.Textarea( attrs={ 'rows': 5, @@ -56,12 +60,13 @@ class DataSourceForm(NetBoxModelForm): # Add backend-specific form fields self.backend_fields = [] - for name, form_field in backend.parameters.items(): - field_name = f'backend_{name}' - self.backend_fields.append(field_name) - self.fields[field_name] = copy.copy(form_field) - if self.instance and self.instance.parameters: - self.fields[field_name].initial = self.instance.parameters.get(name) + if backend: + for name, form_field in backend.parameters.items(): + field_name = f'backend_{name}' + self.backend_fields.append(field_name) + self.fields[field_name] = copy.copy(form_field) + if self.instance and self.instance.parameters: + self.fields[field_name].initial = self.instance.parameters.get(name) def save(self, *args, **kwargs): diff --git a/netbox/core/jobs.py b/netbox/core/jobs.py index d25981920..32b546b20 100644 --- a/netbox/core/jobs.py +++ b/netbox/core/jobs.py @@ -25,7 +25,7 @@ def sync_datasource(job, *args, **kwargs): job.terminate() except Exception as e: - job.terminate(status=JobStatusChoices.STATUS_ERRORED) + job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=str(e)) DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED) if type(e) in (SyncError, JobTimeoutException): logging.error(e) diff --git a/netbox/core/migrations/0006_datasource_type_remove_choices.py b/netbox/core/migrations/0006_datasource_type_remove_choices.py new file mode 100644 index 000000000..0ad8d8854 --- /dev/null +++ b/netbox/core/migrations/0006_datasource_type_remove_choices.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-10-20 17:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_job_created_auto_now'), + ] + + operations = [ + migrations.AlterField( + model_name='datasource', + name='type', + field=models.CharField(max_length=50), + ), + ] diff --git a/netbox/core/migrations/0007_job_add_error_field.py b/netbox/core/migrations/0007_job_add_error_field.py new file mode 100644 index 000000000..e2e173bfd --- /dev/null +++ b/netbox/core/migrations/0007_job_add_error_field.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-10-23 20:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_datasource_type_remove_choices'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='error', + field=models.TextField(blank=True, editable=False), + ), + ] diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index 54a43c7ef..fb764134a 100644 --- a/netbox/core/models/data.py +++ b/netbox/core/models/data.py @@ -45,9 +45,7 @@ class DataSource(JobsMixin, PrimaryModel): ) type = models.CharField( verbose_name=_('type'), - max_length=50, - choices=DataSourceTypeChoices, - default=DataSourceTypeChoices.LOCAL + max_length=50 ) source_url = models.CharField( max_length=200, @@ -96,8 +94,9 @@ class DataSource(JobsMixin, PrimaryModel): def docs_url(self): return f'{settings.STATIC_URL}docs/models/{self._meta.app_label}/{self._meta.model_name}/' - def get_type_color(self): - return DataSourceTypeChoices.colors.get(self.type) + def get_type_display(self): + if backend := registry['data_backends'].get(self.type): + return backend.label def get_status_color(self): return DataSourceStatusChoices.colors.get(self.status) @@ -110,10 +109,6 @@ class DataSource(JobsMixin, PrimaryModel): def backend_class(self): return registry['data_backends'].get(self.type) - @property - def is_local(self): - return self.type == DataSourceTypeChoices.LOCAL - @property def ready_for_sync(self): return self.enabled and self.status not in ( @@ -123,8 +118,14 @@ class DataSource(JobsMixin, PrimaryModel): def clean(self): + # Validate data backend type + if self.type and self.type not in registry['data_backends']: + raise ValidationError({ + 'type': _("Unknown backend type: {type}".format(type=self.type)) + }) + # Ensure URL scheme matches selected type - if self.type == DataSourceTypeChoices.LOCAL and self.url_scheme not in ('file', ''): + if self.backend_class.is_local and self.url_scheme not in ('file', ''): raise ValidationError({ 'source_url': f"URLs for local sources must start with file:// (or specify no scheme)" }) diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 61b0e64fa..4e9a93bfb 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -92,6 +92,11 @@ class Job(models.Model): null=True, blank=True ) + error = models.TextField( + verbose_name=_('error'), + editable=False, + blank=True + ) job_id = models.UUIDField( verbose_name=_('job ID'), unique=True @@ -158,7 +163,7 @@ class Job(models.Model): # Handle webhooks self.trigger_webhooks(event=EVENT_JOB_START) - def terminate(self, status=JobStatusChoices.STATUS_COMPLETED): + def terminate(self, status=JobStatusChoices.STATUS_COMPLETED, error=None): """ Mark the job as completed, optionally specifying a particular termination status. """ @@ -168,6 +173,8 @@ class Job(models.Model): # Mark the job as completed self.status = status + if error: + self.error = error self.completed = timezone.now() self.save() diff --git a/netbox/core/tables/columns.py b/netbox/core/tables/columns.py new file mode 100644 index 000000000..93f1e3901 --- /dev/null +++ b/netbox/core/tables/columns.py @@ -0,0 +1,20 @@ +import django_tables2 as tables + +from netbox.registry import registry + +__all__ = ( + 'BackendTypeColumn', +) + + +class BackendTypeColumn(tables.Column): + """ + Display a data backend type. + """ + def render(self, value): + if backend := registry['data_backends'].get(value): + return backend.label + return value + + def value(self, value): + return value diff --git a/netbox/core/tables/data.py b/netbox/core/tables/data.py index 1ecc42369..4059ea9bc 100644 --- a/netbox/core/tables/data.py +++ b/netbox/core/tables/data.py @@ -3,6 +3,7 @@ import django_tables2 as tables from core.models import * from netbox.tables import NetBoxTable, columns +from .columns import BackendTypeColumn __all__ = ( 'DataFileTable', @@ -15,8 +16,8 @@ class DataSourceTable(NetBoxTable): verbose_name=_('Name'), linkify=True ) - type = columns.ChoiceFieldColumn( - verbose_name=_('Type'), + type = BackendTypeColumn( + verbose_name=_('Type') ) status = columns.ChoiceFieldColumn( verbose_name=_('Status'), @@ -34,8 +35,8 @@ class DataSourceTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = DataSource fields = ( - 'pk', 'id', 'name', 'type', 'status', 'enabled', 'source_url', 'description', 'comments', 'parameters', 'created', - 'last_updated', 'file_count', + 'pk', 'id', 'name', 'type', 'status', 'enabled', 'source_url', 'description', 'comments', 'parameters', + 'created', 'last_updated', 'file_count', ) default_columns = ('pk', 'name', 'type', 'status', 'enabled', 'description', 'file_count') diff --git a/netbox/core/tables/jobs.py b/netbox/core/tables/jobs.py index 32ca67f7f..3388aee19 100644 --- a/netbox/core/tables/jobs.py +++ b/netbox/core/tables/jobs.py @@ -47,7 +47,7 @@ class JobTable(NetBoxTable): model = Job fields = ( 'pk', 'id', 'object_type', 'object', 'name', 'status', 'created', 'scheduled', 'interval', 'started', - 'completed', 'user', 'job_id', + 'completed', 'user', 'error', 'job_id', ) default_columns = ( 'pk', 'id', 'object_type', 'object', 'name', 'status', 'created', 'started', 'completed', 'user', diff --git a/netbox/core/tests/test_api.py b/netbox/core/tests/test_api.py index dc6d6a5ce..cd25761f0 100644 --- a/netbox/core/tests/test_api.py +++ b/netbox/core/tests/test_api.py @@ -2,7 +2,6 @@ from django.urls import reverse from django.utils import timezone from utilities.testing import APITestCase, APIViewTestCases -from ..choices import * from ..models import * @@ -26,26 +25,26 @@ class DataSourceTest(APIViewTestCases.APIViewTestCase): @classmethod def setUpTestData(cls): data_sources = ( - DataSource(name='Data Source 1', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source1/'), - DataSource(name='Data Source 2', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source2/'), - DataSource(name='Data Source 3', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source3/'), + DataSource(name='Data Source 1', type='local', source_url='file:///var/tmp/source1/'), + DataSource(name='Data Source 2', type='local', source_url='file:///var/tmp/source2/'), + DataSource(name='Data Source 3', type='local', source_url='file:///var/tmp/source3/'), ) DataSource.objects.bulk_create(data_sources) cls.create_data = [ { 'name': 'Data Source 4', - 'type': DataSourceTypeChoices.GIT, + 'type': 'git', 'source_url': 'https://example.com/git/source4' }, { 'name': 'Data Source 5', - 'type': DataSourceTypeChoices.GIT, + 'type': 'git', 'source_url': 'https://example.com/git/source5' }, { 'name': 'Data Source 6', - 'type': DataSourceTypeChoices.GIT, + 'type': 'git', 'source_url': 'https://example.com/git/source6' }, ] @@ -63,7 +62,7 @@ class DataFileTest( def setUpTestData(cls): datasource = DataSource.objects.create( name='Data Source 1', - type=DataSourceTypeChoices.LOCAL, + type='local', source_url='file:///var/tmp/source1/' ) diff --git a/netbox/core/tests/test_filtersets.py b/netbox/core/tests/test_filtersets.py index e1e916f70..2f60c7522 100644 --- a/netbox/core/tests/test_filtersets.py +++ b/netbox/core/tests/test_filtersets.py @@ -18,21 +18,21 @@ class DataSourceTestCase(TestCase, ChangeLoggedFilterSetTests): data_sources = ( DataSource( name='Data Source 1', - type=DataSourceTypeChoices.LOCAL, + type='local', source_url='file:///var/tmp/source1/', status=DataSourceStatusChoices.NEW, enabled=True ), DataSource( name='Data Source 2', - type=DataSourceTypeChoices.LOCAL, + type='local', source_url='file:///var/tmp/source2/', status=DataSourceStatusChoices.SYNCING, enabled=True ), DataSource( name='Data Source 3', - type=DataSourceTypeChoices.GIT, + type='git', source_url='https://example.com/git/source3', status=DataSourceStatusChoices.COMPLETED, enabled=False @@ -45,7 +45,7 @@ class DataSourceTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): - params = {'type': [DataSourceTypeChoices.LOCAL]} + params = {'type': ['local']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_enabled(self): @@ -66,9 +66,9 @@ class DataFileTestCase(TestCase, ChangeLoggedFilterSetTests): @classmethod def setUpTestData(cls): data_sources = ( - DataSource(name='Data Source 1', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source1/'), - DataSource(name='Data Source 2', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source2/'), - DataSource(name='Data Source 3', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source3/'), + DataSource(name='Data Source 1', type='local', source_url='file:///var/tmp/source1/'), + DataSource(name='Data Source 2', type='local', source_url='file:///var/tmp/source2/'), + DataSource(name='Data Source 3', type='local', source_url='file:///var/tmp/source3/'), ) DataSource.objects.bulk_create(data_sources) diff --git a/netbox/core/tests/test_views.py b/netbox/core/tests/test_views.py index 4a50a8d05..16d07f376 100644 --- a/netbox/core/tests/test_views.py +++ b/netbox/core/tests/test_views.py @@ -1,7 +1,6 @@ from django.utils import timezone from utilities.testing import ViewTestCases, create_tags -from ..choices import * from ..models import * @@ -11,9 +10,9 @@ class DataSourceTestCase(ViewTestCases.PrimaryObjectViewTestCase): @classmethod def setUpTestData(cls): data_sources = ( - DataSource(name='Data Source 1', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source1/'), - DataSource(name='Data Source 2', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source2/'), - DataSource(name='Data Source 3', type=DataSourceTypeChoices.LOCAL, source_url='file:///var/tmp/source3/'), + DataSource(name='Data Source 1', type='local', source_url='file:///var/tmp/source1/'), + DataSource(name='Data Source 2', type='local', source_url='file:///var/tmp/source2/'), + DataSource(name='Data Source 3', type='local', source_url='file:///var/tmp/source3/'), ) DataSource.objects.bulk_create(data_sources) @@ -21,7 +20,7 @@ class DataSourceTestCase(ViewTestCases.PrimaryObjectViewTestCase): cls.form_data = { 'name': 'Data Source X', - 'type': DataSourceTypeChoices.GIT, + 'type': 'git', 'source_url': 'http:///exmaple/com/foo/bar/', 'description': 'Something', 'comments': 'Foo bar baz', @@ -29,10 +28,10 @@ class DataSourceTestCase(ViewTestCases.PrimaryObjectViewTestCase): } cls.csv_data = ( - f"name,type,source_url,enabled", - f"Data Source 4,{DataSourceTypeChoices.LOCAL},file:///var/tmp/source4/,true", - f"Data Source 5,{DataSourceTypeChoices.LOCAL},file:///var/tmp/source4/,true", - f"Data Source 6,{DataSourceTypeChoices.GIT},http:///exmaple/com/foo/bar/,false", + "name,type,source_url,enabled", + "Data Source 4,local,file:///var/tmp/source4/,true", + "Data Source 5,local,file:///var/tmp/source4/,true", + "Data Source 6,git,http:///exmaple/com/foo/bar/,false", ) cls.csv_update_data = ( @@ -60,7 +59,7 @@ class DataFileTestCase( def setUpTestData(cls): datasource = DataSource.objects.create( name='Data Source 1', - type=DataSourceTypeChoices.LOCAL, + type='local', source_url='file:///var/tmp/source1/' ) diff --git a/netbox/core/views.py b/netbox/core/views.py index e3c1a67aa..d16fa4ece 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -100,7 +100,9 @@ class DataFileListView(generic.ObjectListView): filterset = filtersets.DataFileFilterSet filterset_form = forms.DataFileFilterForm table = tables.DataFileTable - actions = ('bulk_delete',) + actions = { + 'bulk_delete': {'delete'}, + } @register_model_view(DataFile) @@ -128,7 +130,10 @@ class JobListView(generic.ObjectListView): filterset = filtersets.JobFilterSet filterset_form = forms.JobFilterForm table = tables.JobTable - actions = ('export', 'delete', 'bulk_delete') + actions = { + 'export': {'view'}, + 'bulk_delete': {'delete'}, + } class JobView(generic.ObjectView): diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index b43611dad..32dcdc5bb 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -343,9 +343,9 @@ class DeviceTypeSerializer(NetBoxModelSerializer): model = DeviceType fields = [ 'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', - 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', - 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', - 'console_port_template_count', 'console_server_port_template_count', 'power_port_template_count', + 'exclude_from_utilization', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', + 'front_image', 'rear_image', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'device_count', 'console_port_template_count', 'console_server_port_template_count', 'power_port_template_count', 'power_outlet_template_count', 'interface_template_count', 'front_port_template_count', 'rear_port_template_count', 'device_bay_template_count', 'module_bay_template_count', 'inventory_item_template_count', diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index e1d4a330a..2ba24e0aa 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -80,10 +80,10 @@ class RackWidthChoices(ChoiceSet): WIDTH_23IN = 23 CHOICES = ( - (WIDTH_10IN, _('10 inches')), - (WIDTH_19IN, _('19 inches')), - (WIDTH_21IN, _('21 inches')), - (WIDTH_23IN, _('23 inches')), + (WIDTH_10IN, _('{n} inches').format(n=10)), + (WIDTH_19IN, _('{n} inches').format(n=19)), + (WIDTH_21IN, _('{n} inches').format(n=21)), + (WIDTH_23IN, _('{n} inches').format(n=23)), ) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index d600667d7..c65110d9a 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -496,7 +496,8 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet): class Meta: model = DeviceType fields = [ - 'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', + 'id', 'model', 'slug', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth', 'subdevice_role', + 'airflow', 'weight', 'weight_unit', ] def search(self, queryset, name, value): diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index cacf1f72b..9c64d8a19 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -420,6 +420,11 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): widget=BulkEditNullBooleanSelect(), label=_('Is full depth') ) + exclude_from_utilization = forms.NullBooleanField( + required=False, + widget=BulkEditNullBooleanSelect(), + label=_('Exclude from utilization') + ) airflow = forms.ChoiceField( label=_('Airflow'), choices=add_blank_choice(DeviceAirflowChoices), @@ -445,7 +450,10 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): model = DeviceType fieldsets = ( - (_('Device Type'), ('manufacturer', 'default_platform', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')), + (_('Device Type'), ( + 'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth', + 'airflow', 'description', + )), (_('Weight'), ('weight', 'weight_unit')), ) nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments') diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index e41e875e4..d63873b59 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -335,8 +335,8 @@ class DeviceTypeImportForm(NetBoxModelImportForm): class Meta: model = DeviceType fields = [ - 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', - 'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments', 'tags', + 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'exclude_from_utilization', + 'is_full_depth', 'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments', 'tags', ] diff --git a/netbox/dcim/forms/common.py b/netbox/dcim/forms/common.py index 77543af12..3be4d08e8 100644 --- a/netbox/dcim/forms/common.py +++ b/netbox/dcim/forms/common.py @@ -116,17 +116,17 @@ class ModuleCommonForm(forms.Form): # It is not possible to adopt components already belonging to a module if adopt_components and existing_item and existing_item.module: raise forms.ValidationError( - _("Cannot adopt {name} '{resolved_name}' as it already belongs to a module").format( - name=template.component_model.__name__, - resolved_name=resolved_name + _("Cannot adopt {model} {name} as it already belongs to a module").format( + model=template.component_model.__name__, + name=resolved_name ) ) # If we are not adopting components we error if the component exists if not adopt_components and resolved_name in installed_components: raise forms.ValidationError( - _("{name} - {resolved_name} already exists").format( - name=template.component_model.__name__, - resolved_name=resolved_name + _("A {model} named {name} already exists").format( + model=template.component_model.__name__, + name=resolved_name ) ) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 93e214598..3d626d201 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -302,7 +302,8 @@ class DeviceTypeForm(NetBoxModelForm): fieldsets = ( (_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')), (_('Chassis'), ( - 'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit', + 'u_height', 'exclude_from_utilization', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', + 'weight', 'weight_unit', )), (_('Images'), ('front_image', 'rear_image')), ) @@ -310,9 +311,9 @@ class DeviceTypeForm(NetBoxModelForm): class Meta: model = DeviceType fields = [ - 'manufacturer', 'model', 'slug', 'default_platform', 'part_number', 'u_height', 'is_full_depth', - 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description', - 'comments', 'tags', + 'manufacturer', 'model', 'slug', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', + 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', + 'description', 'comments', 'tags', ] widgets = { 'front_image': ClearableFileInput(attrs={ diff --git a/netbox/dcim/migrations/0182_devicetype_exclude_from_utilization.py b/netbox/dcim/migrations/0182_devicetype_exclude_from_utilization.py new file mode 100644 index 000000000..6943387c5 --- /dev/null +++ b/netbox/dcim/migrations/0182_devicetype_exclude_from_utilization.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.5 on 2023-10-20 22:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('dcim', '0181_rename_device_role_device_role'), + ] + + operations = [ + migrations.AddField( + model_name='devicetype', + name='exclude_from_utilization', + field=models.BooleanField(default=False), + ), + ] diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 86b6d85fe..5110835f4 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -534,14 +534,16 @@ class FrontPortTemplate(ModularComponentTemplateModel): # Validate rear port assignment if self.rear_port.device_type != self.device_type: raise ValidationError( - _("Rear port ({}) must belong to the same device type").format(self.rear_port) + _("Rear port ({name}) must belong to the same device type").format(name=self.rear_port) ) # Validate rear port position assignment if self.rear_port_position > self.rear_port.positions: raise ValidationError( - _("Invalid rear port position ({}); rear port {} has only {} positions").format( - self.rear_port_position, self.rear_port.name, self.rear_port.positions + _("Invalid rear port position ({position}); rear port {name} has only {count} positions").format( + position=self.rear_port_position, + name=self.rear_port.name, + count=self.rear_port.positions ) ) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index c9ebf898d..07c1c70f6 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -106,6 +106,11 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): default=1.0, verbose_name=_('height (U)') ) + exclude_from_utilization = models.BooleanField( + default=False, + verbose_name=_('exclude from utilization'), + help_text=_('Exclude from rack utilization calculations.') + ) is_full_depth = models.BooleanField( default=True, verbose_name=_('is full depth'), @@ -297,8 +302,10 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): ) if d.position not in u_available: raise ValidationError({ - 'u_height': _("Device {} in rack {} does not have sufficient space to accommodate a height of " - "{}U").format(d, d.rack, self.u_height) + 'u_height': _( + "Device {device} in rack {rack} does not have sufficient space to accommodate a " + "height of {height}U" + ).format(device=d, rack=d.rack, height=self.u_height) }) # If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position. @@ -915,7 +922,7 @@ class Device( if self.primary_ip4: if self.primary_ip4.family != 4: raise ValidationError({ - 'primary_ip4': _("{primary_ip4} is not an IPv4 address.").format(primary_ip4=self.primary_ip4) + 'primary_ip4': _("{ip} is not an IPv4 address.").format(ip=self.primary_ip4) }) if self.primary_ip4.assigned_object in vc_interfaces: pass @@ -924,13 +931,13 @@ class Device( else: raise ValidationError({ 'primary_ip4': _( - "The specified IP address ({primary_ip4}) is not assigned to this device." - ).format(primary_ip4=self.primary_ip4) + "The specified IP address ({ip}) is not assigned to this device." + ).format(ip=self.primary_ip4) }) if self.primary_ip6: if self.primary_ip6.family != 6: raise ValidationError({ - 'primary_ip6': _("{primary_ip6} is not an IPv6 address.").format(primary_ip6=self.primary_ip6m) + 'primary_ip6': _("{ip} is not an IPv6 address.").format(ip=self.primary_ip6) }) if self.primary_ip6.assigned_object in vc_interfaces: pass @@ -939,8 +946,8 @@ class Device( else: raise ValidationError({ 'primary_ip6': _( - "The specified IP address ({primary_ip6}) is not assigned to this device." - ).format(primary_ip6=self.primary_ip6) + "The specified IP address ({ip}) is not assigned to this device." + ).format(ip=self.primary_ip6) }) if self.oob_ip: if self.oob_ip.assigned_object in vc_interfaces: @@ -958,17 +965,19 @@ class Device( raise ValidationError({ 'platform': _( "The assigned platform is limited to {platform_manufacturer} device types, but this device's " - "type belongs to {device_type_manufacturer}." + "type belongs to {devicetype_manufacturer}." ).format( platform_manufacturer=self.platform.manufacturer, - device_type_manufacturer=self.device_type.manufacturer + devicetype_manufacturer=self.device_type.manufacturer ) }) # A Device can only be assigned to a Cluster in the same Site (or no Site) if self.cluster and self.cluster.site is not None and self.cluster.site != self.site: raise ValidationError({ - 'cluster': _("The assigned cluster belongs to a different site ({})").format(self.cluster.site) + 'cluster': _("The assigned cluster belongs to a different site ({site})").format( + site=self.cluster.site + ) }) # Validate virtual chassis assignment @@ -1440,8 +1449,8 @@ class VirtualDeviceContext(PrimaryModel): if primary_ip.family != family: raise ValidationError({ f'primary_ip{family}': _( - "{primary_ip} is not an IPv{family} address." - ).format(family=family, primary_ip=primary_ip) + "{ip} is not an IPv{family} address." + ).format(family=family, ip=primary_ip) }) device_interfaces = self.device.vc_interfaces(if_master=False) if primary_ip.assigned_object not in device_interfaces: diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index ef0dde4da..0d4b844f9 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -357,7 +357,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, PrimaryModel, WeightMixin): return [u for u in elevation.values()] - def get_available_units(self, u_height=1, rack_face=None, exclude=None): + def get_available_units(self, u_height=1, rack_face=None, exclude=None, ignore_excluded_devices=False): """ Return a list of units within the rack available to accommodate a device of a given U height (default 1). Optionally exclude one or more devices when calculating empty units (needed when moving a device from one @@ -366,9 +366,13 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, PrimaryModel, WeightMixin): :param u_height: Minimum number of contiguous free units required :param rack_face: The face of the rack (front or rear) required; 'None' if device is full depth :param exclude: List of devices IDs to exclude (useful when moving a device within a rack) + :param ignore_excluded_devices: Ignore devices that are marked to exclude from utilization calculations """ # Gather all devices which consume U space within the rack devices = self.devices.prefetch_related('device_type').filter(position__gte=1) + if ignore_excluded_devices: + devices = devices.exclude(device_type__exclude_from_utilization=True) + if exclude is not None: devices = devices.exclude(pk__in=exclude) @@ -453,7 +457,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, PrimaryModel, WeightMixin): """ # Determine unoccupied units total_units = len(list(self.units)) - available_units = self.get_available_units(u_height=0.5) + available_units = self.get_available_units(u_height=0.5, ignore_excluded_devices=True) # Remove reserved units for ru in self.get_reserved_units(): @@ -558,9 +562,9 @@ class RackReservation(PrimaryModel): invalid_units = [u for u in self.units if u not in self.rack.units] if invalid_units: raise ValidationError({ - 'units': _("Invalid unit(s) for {}U rack: {}").format( - self.rack.u_height, - ', '.join([str(u) for u in invalid_units]), + 'units': _("Invalid unit(s) for {height}U rack: {unit_list}").format( + height=self.rack.u_height, + unit_list=', '.join([str(u) for u in invalid_units]) ), }) @@ -571,8 +575,8 @@ class RackReservation(PrimaryModel): conflicting_units = [u for u in self.units if u in reserved_units] if conflicting_units: raise ValidationError({ - 'units': _('The following units have already been reserved: {}').format( - ', '.join([str(u) for u in conflicting_units]), + 'units': _('The following units have already been reserved: {unit_list}').format( + unit_list=', '.join([str(u) for u in conflicting_units]) ) }) diff --git a/netbox/dcim/svg/cables.py b/netbox/dcim/svg/cables.py index acc4fcad9..31e090078 100644 --- a/netbox/dcim/svg/cables.py +++ b/netbox/dcim/svg/cables.py @@ -159,6 +159,7 @@ class CableTraceSVG: labels.append(location_label) elif instance._meta.model_name == 'circuit': labels[0] = f'Circuit {instance}' + labels.append(instance.type) labels.append(instance.provider) if instance.description: labels.append(instance.description) @@ -181,6 +182,8 @@ class CableTraceSVG: if hasattr(instance, 'role'): # Device return instance.role.color + elif instance._meta.model_name == 'circuit' and instance.type.color: + return instance.type.color else: # Other parent object return 'e0e0e0' diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 7d8884fc1..fad238c6e 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -98,6 +98,7 @@ class DeviceTypeTable(NetBoxTable): verbose_name=_('U Height'), template_code='{{ value|floatformat }}' ) + exclude_from_utilization = columns.BooleanColumn() weight = columns.TemplateColumn( verbose_name=_('Weight'), template_code=WEIGHT, @@ -142,9 +143,9 @@ class DeviceTypeTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = models.DeviceType fields = ( - 'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height', 'is_full_depth', - 'subdevice_role', 'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', - 'last_updated', + 'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height', + 'exclude_from_utilization', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', + 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count', diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 2e5ae0d5c..741a615d4 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -238,6 +238,40 @@ class RackTestCase(TestCase): # Check that Device1 is now assigned to Site B self.assertEqual(Device.objects.get(pk=device1.pk).site, site_b) + def test_utilization(self): + site = Site.objects.first() + rack = Rack.objects.first() + + Device( + name='Device 1', + role=DeviceRole.objects.first(), + device_type=DeviceType.objects.first(), + site=site, + rack=rack, + position=1 + ).save() + rack.refresh_from_db() + self.assertEqual(rack.get_utilization(), 1 / 42 * 100) + + # create device excluded from utilization calculations + dt = DeviceType.objects.create( + manufacturer=Manufacturer.objects.first(), + model='Device Type 4', + slug='device-type-4', + u_height=1, + exclude_from_utilization=True + ) + Device( + name='Device 2', + role=DeviceRole.objects.first(), + device_type=dt, + site=site, + rack=rack, + position=5 + ).save() + rack.refresh_from_db() + self.assertEqual(rack.get_utilization(), 1 / 42 * 100) + class DeviceTestCase(TestCase): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 7c75dd26e..0f5768173 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -20,6 +20,7 @@ from circuits.models import Circuit, CircuitTermination from extras.views import ObjectConfigContextView from ipam.models import ASN, IPAddress, Prefix, VLAN, VLANGroup from ipam.tables import InterfaceVLANTable +from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.views import generic from tenancy.views import ObjectContactsView from utilities.forms import ConfirmationForm @@ -46,15 +47,11 @@ CABLE_TERMINATION_TYPES = { class DeviceComponentsView(generic.ObjectChildrenView): - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename', 'bulk_disconnect') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, 'bulk_disconnect': {'change'}, - }) + } queryset = Device.objects.all() def get_children(self, request, parent): @@ -1977,7 +1974,10 @@ class DeviceModuleBaysView(DeviceComponentsView): table = tables.DeviceModuleBayTable filterset = filtersets.ModuleBayFilterSet template_name = 'dcim/device/modulebays.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') + actions = { + **DEFAULT_ACTION_PERMISSIONS, + 'bulk_rename': {'change'}, + } tab = ViewTab( label=_('Module Bays'), badge=lambda obj: obj.module_bay_count, @@ -1993,7 +1993,10 @@ class DeviceDeviceBaysView(DeviceComponentsView): table = tables.DeviceDeviceBayTable filterset = filtersets.DeviceBayFilterSet template_name = 'dcim/device/devicebays.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') + actions = { + **DEFAULT_ACTION_PERMISSIONS, + 'bulk_rename': {'change'}, + } tab = ViewTab( label=_('Device Bays'), badge=lambda obj: obj.device_bay_count, @@ -2005,11 +2008,14 @@ class DeviceDeviceBaysView(DeviceComponentsView): @register_model_view(Device, 'inventory') class DeviceInventoryView(DeviceComponentsView): - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') child_model = InventoryItem table = tables.DeviceInventoryItemTable filterset = filtersets.InventoryItemFilterSet template_name = 'dcim/device/inventory.html' + actions = { + **DEFAULT_ACTION_PERMISSIONS, + 'bulk_rename': {'change'}, + } tab = ViewTab( label=_('Inventory Items'), badge=lambda obj: obj.inventory_item_count, @@ -2187,14 +2193,10 @@ class ConsolePortListView(generic.ObjectListView): filterset_form = forms.ConsolePortFilterForm table = tables.ConsolePortTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(ConsolePort) @@ -2259,14 +2261,10 @@ class ConsoleServerPortListView(generic.ObjectListView): filterset_form = forms.ConsoleServerPortFilterForm table = tables.ConsoleServerPortTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(ConsoleServerPort) @@ -2331,14 +2329,10 @@ class PowerPortListView(generic.ObjectListView): filterset_form = forms.PowerPortFilterForm table = tables.PowerPortTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(PowerPort) @@ -2403,14 +2397,10 @@ class PowerOutletListView(generic.ObjectListView): filterset_form = forms.PowerOutletFilterForm table = tables.PowerOutletTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(PowerOutlet) @@ -2475,14 +2465,10 @@ class InterfaceListView(generic.ObjectListView): filterset_form = forms.InterfaceFilterForm table = tables.InterfaceTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(Interface) @@ -2595,14 +2581,10 @@ class FrontPortListView(generic.ObjectListView): filterset_form = forms.FrontPortFilterForm table = tables.FrontPortTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(FrontPort) @@ -2667,14 +2649,10 @@ class RearPortListView(generic.ObjectListView): filterset_form = forms.RearPortFilterForm table = tables.RearPortTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(RearPort) @@ -2739,14 +2717,10 @@ class ModuleBayListView(generic.ObjectListView): filterset_form = forms.ModuleBayFilterForm table = tables.ModuleBayTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(ModuleBay) @@ -2803,14 +2777,10 @@ class DeviceBayListView(generic.ObjectListView): filterset_form = forms.DeviceBayFilterForm table = tables.DeviceBayTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(DeviceBay) @@ -2936,14 +2906,10 @@ class InventoryItemListView(generic.ObjectListView): filterset_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable template_name = 'dcim/component_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, + actions = { + **DEFAULT_ACTION_PERMISSIONS, 'bulk_rename': {'change'}, - }) + } @register_model_view(InventoryItem) @@ -3175,7 +3141,12 @@ class CableListView(generic.ObjectListView): filterset = filtersets.CableFilterSet filterset_form = forms.CableFilterForm table = tables.CableTable - actions = ('import', 'export', 'bulk_edit', 'bulk_delete') + actions = { + 'import': {'add'}, + 'export': {'view'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + } @register_model_view(Cable) @@ -3269,7 +3240,9 @@ class ConsoleConnectionsListView(generic.ObjectListView): filterset_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable template_name = 'dcim/connections_list.html' - actions = ('export',) + actions = { + 'export': {'view'}, + } def get_extra_context(self, request): return { @@ -3283,7 +3256,9 @@ class PowerConnectionsListView(generic.ObjectListView): filterset_form = forms.PowerConnectionFilterForm table = tables.PowerConnectionTable template_name = 'dcim/connections_list.html' - actions = ('export',) + actions = { + 'export': {'view'}, + } def get_extra_context(self, request): return { @@ -3297,7 +3272,9 @@ class InterfaceConnectionsListView(generic.ObjectListView): filterset_form = forms.InterfaceConnectionFilterForm table = tables.InterfaceConnectionTable template_name = 'dcim/connections_list.html' - actions = ('export',) + actions = { + 'export': {'view'}, + } def get_extra_context(self, request): return { diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 83a346420..fd2ce8f2d 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -491,7 +491,7 @@ class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=ConfigFormMe (_('Security'), ('ALLOWED_URL_SCHEMES',)), (_('Banners'), ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM')), (_('Pagination'), ('PAGINATE_COUNT', 'MAX_PAGE_SIZE')), - (_('Validation'), ('CUSTOM_VALIDATORS',)), + (_('Validation'), ('CUSTOM_VALIDATORS', 'PROTECTION_RULES')), (_('User Preferences'), ('DEFAULT_USER_PREFERENCES',)), (_('Miscellaneous'), ( 'MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL', @@ -508,6 +508,7 @@ class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=ConfigFormMe 'BANNER_TOP': forms.Textarea(attrs={'class': 'font-monospace'}), 'BANNER_BOTTOM': forms.Textarea(attrs={'class': 'font-monospace'}), 'CUSTOM_VALIDATORS': forms.Textarea(attrs={'class': 'font-monospace'}), + 'PROTECTION_RULES': forms.Textarea(attrs={'class': 'font-monospace'}), 'comment': forms.Textarea(), } diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py index d9a9f41ae..3cf70281c 100644 --- a/netbox/extras/management/commands/runscript.py +++ b/netbox/extras/management/commands/runscript.py @@ -59,7 +59,7 @@ class Command(BaseCommand): logger.error(f"Exception raised during script execution: {e}") clear_webhooks.send(request) job.data = ScriptOutputSerializer(script).data - job.terminate(status=JobStatusChoices.STATUS_ERRORED) + job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=str(e)) logger.info(f"Script completed in {job.duration}") diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 2bed464bb..2cb12ed5b 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -287,8 +287,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): except ValidationError as err: raise ValidationError({ 'default': _( - 'Invalid default value "{default}": {message}' - ).format(default=self.default, message=err.message) + 'Invalid default value "{value}": {error}' + ).format(value=self.default, error=err.message) }) # Minimum/maximum values can be set only for numeric fields @@ -332,8 +332,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): elif self.object_type: raise ValidationError({ 'object_type': _( - "{type_display} fields may not define an object type.") - .format(type_display=self.get_type_display()) + "{type} fields may not define an object type.") + .format(type=self.get_type_display()) }) def serialize(self, value): diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index f60462f3d..31ea1ce09 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -1,148 +1,9 @@ -import collections -from importlib import import_module - -from django.apps import AppConfig -from django.core.exceptions import ImproperlyConfigured -from django.utils.module_loading import import_string -from packaging import version - -from netbox.registry import registry -from netbox.search import register_search from .navigation import * from .registration import * from .templates import * from .utils import * - -# Initialize plugin registry -registry['plugins'].update({ - 'graphql_schemas': [], - 'menus': [], - 'menu_items': {}, - 'preferences': {}, - 'template_extensions': collections.defaultdict(list), -}) - -DEFAULT_RESOURCE_PATHS = { - 'search_indexes': 'search.indexes', - 'graphql_schema': 'graphql.schema', - 'menu': 'navigation.menu', - 'menu_items': 'navigation.menu_items', - 'template_extensions': 'template_content.template_extensions', - 'user_preferences': 'preferences.preferences', -} +from netbox.plugins import PluginConfig -# -# Plugin AppConfig class -# - -class PluginConfig(AppConfig): - """ - Subclass of Django's built-in AppConfig class, to be used for NetBox plugins. - """ - # Plugin metadata - author = '' - author_email = '' - description = '' - version = '' - - # Root URL path under /plugins. If not set, the plugin's label will be used. - base_url = None - - # Minimum/maximum compatible versions of NetBox - min_version = None - max_version = None - - # Default configuration parameters - default_settings = {} - - # Mandatory configuration parameters - required_settings = [] - - # Middleware classes provided by the plugin - middleware = [] - - # Django-rq queues dedicated to the plugin - queues = [] - - # Django apps to append to INSTALLED_APPS when plugin requires them. - django_apps = [] - - # Optional plugin resources - search_indexes = None - graphql_schema = None - menu = None - menu_items = None - template_extensions = None - user_preferences = None - - def _load_resource(self, name): - # Import from the configured path, if defined. - if path := getattr(self, name, None): - return import_string(f"{self.__module__}.{path}") - - # Fall back to the resource's default path. Return None if the module has not been provided. - default_path = f'{self.__module__}.{DEFAULT_RESOURCE_PATHS[name]}' - default_module, resource_name = default_path.rsplit('.', 1) - try: - module = import_module(default_module) - return getattr(module, resource_name, None) - except ModuleNotFoundError: - pass - - def ready(self): - plugin_name = self.name.rsplit('.', 1)[-1] - - # Register search extensions (if defined) - search_indexes = self._load_resource('search_indexes') or [] - for idx in search_indexes: - register_search(idx) - - # Register template content (if defined) - if template_extensions := self._load_resource('template_extensions'): - register_template_extensions(template_extensions) - - # Register navigation menu and/or menu items (if defined) - if menu := self._load_resource('menu'): - register_menu(menu) - if menu_items := self._load_resource('menu_items'): - register_menu_items(self.verbose_name, menu_items) - - # Register GraphQL schema (if defined) - if graphql_schema := self._load_resource('graphql_schema'): - register_graphql_schema(graphql_schema) - - # Register user preferences (if defined) - if user_preferences := self._load_resource('user_preferences'): - register_user_preferences(plugin_name, user_preferences) - - @classmethod - def validate(cls, user_config, netbox_version): - - # Enforce version constraints - current_version = version.parse(netbox_version) - if cls.min_version is not None: - min_version = version.parse(cls.min_version) - if current_version < min_version: - raise ImproperlyConfigured( - f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}." - ) - if cls.max_version is not None: - max_version = version.parse(cls.max_version) - if current_version > max_version: - raise ImproperlyConfigured( - f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}." - ) - - # Verify required configuration settings - for setting in cls.required_settings: - if setting not in user_config: - raise ImproperlyConfigured( - f"Plugin {cls.__module__} requires '{setting}' to be present in the PLUGINS_CONFIG section of " - f"configuration.py." - ) - - # Apply default configuration values - for setting, value in cls.default_settings.items(): - if setting not in user_config: - user_config[setting] = value +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/plugins/navigation.py b/netbox/extras/plugins/navigation.py index 2075c97b6..08d1baa54 100644 --- a/netbox/extras/plugins/navigation.py +++ b/netbox/extras/plugins/navigation.py @@ -1,72 +1,7 @@ -from netbox.navigation import MenuGroup -from utilities.choices import ButtonColorChoices -from django.utils.text import slugify +import warnings -__all__ = ( - 'PluginMenu', - 'PluginMenuButton', - 'PluginMenuItem', -) +from netbox.plugins.navigation import * -class PluginMenu: - icon_class = 'mdi mdi-puzzle' - - def __init__(self, label, groups, icon_class=None): - self.label = label - self.groups = [ - MenuGroup(label, items) for label, items in groups - ] - if icon_class is not None: - self.icon_class = icon_class - - @property - def name(self): - return slugify(self.label) - - -class PluginMenuItem: - """ - This class represents a navigation menu item. This constitutes primary link and its text, but also allows for - specifying additional link buttons that appear to the right of the item in the van menu. - - Links are specified as Django reverse URL strings. - Buttons are each specified as a list of PluginMenuButton instances. - """ - permissions = [] - buttons = [] - - def __init__(self, link, link_text, staff_only=False, permissions=None, buttons=None): - self.link = link - self.link_text = link_text - self.staff_only = staff_only - if permissions is not None: - if type(permissions) not in (list, tuple): - raise TypeError("Permissions must be passed as a tuple or list.") - self.permissions = permissions - if buttons is not None: - if type(buttons) not in (list, tuple): - raise TypeError("Buttons must be passed as a tuple or list.") - self.buttons = buttons - - -class PluginMenuButton: - """ - This class represents a button within a PluginMenuItem. Note that button colors should come from - ButtonColorChoices. - """ - color = ButtonColorChoices.DEFAULT - permissions = [] - - def __init__(self, link, title, icon_class, color=None, permissions=None): - self.link = link - self.title = title - self.icon_class = icon_class - if permissions is not None: - if type(permissions) not in (list, tuple): - raise TypeError("Permissions must be passed as a tuple or list.") - self.permissions = permissions - if color is not None: - if color not in ButtonColorChoices.values(): - raise ValueError("Button color must be a choice within ButtonColorChoices.") - self.color = color +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/plugins/registration.py b/netbox/extras/plugins/registration.py index 5b7e58172..8d2d85573 100644 --- a/netbox/extras/plugins/registration.py +++ b/netbox/extras/plugins/registration.py @@ -1,64 +1,7 @@ -import inspect +import warnings -from netbox.registry import registry -from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem -from .templates import PluginTemplateExtension - -__all__ = ( - 'register_graphql_schema', - 'register_menu', - 'register_menu_items', - 'register_template_extensions', - 'register_user_preferences', -) +from netbox.plugins.registration import * -def register_template_extensions(class_list): - """ - Register a list of PluginTemplateExtension classes - """ - # Validation - for template_extension in class_list: - if not inspect.isclass(template_extension): - raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!") - if not issubclass(template_extension, PluginTemplateExtension): - raise TypeError(f"{template_extension} is not a subclass of extras.plugins.PluginTemplateExtension!") - if template_extension.model is None: - raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!") - - registry['plugins']['template_extensions'][template_extension.model].append(template_extension) - - -def register_menu(menu): - if not isinstance(menu, PluginMenu): - raise TypeError(f"{menu} must be an instance of extras.plugins.PluginMenu") - registry['plugins']['menus'].append(menu) - - -def register_menu_items(section_name, class_list): - """ - Register a list of PluginMenuItem instances for a given menu section (e.g. plugin name) - """ - # Validation - for menu_link in class_list: - if not isinstance(menu_link, PluginMenuItem): - raise TypeError(f"{menu_link} must be an instance of extras.plugins.PluginMenuItem") - for button in menu_link.buttons: - if not isinstance(button, PluginMenuButton): - raise TypeError(f"{button} must be an instance of extras.plugins.PluginMenuButton") - - registry['plugins']['menu_items'][section_name] = class_list - - -def register_graphql_schema(graphql_schema): - """ - Register a GraphQL schema class for inclusion in NetBox's GraphQL API. - """ - registry['plugins']['graphql_schemas'].append(graphql_schema) - - -def register_user_preferences(plugin_name, preferences): - """ - Register a list of user preferences defined by a plugin. - """ - registry['plugins']['preferences'][plugin_name] = preferences +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/plugins/templates.py b/netbox/extras/plugins/templates.py index e9b9a9dca..0e09f33d2 100644 --- a/netbox/extras/plugins/templates.py +++ b/netbox/extras/plugins/templates.py @@ -1,73 +1,7 @@ -from django.template.loader import get_template +import warnings -__all__ = ( - 'PluginTemplateExtension', -) +from netbox.plugins.templates import * -class PluginTemplateExtension: - """ - This class is used to register plugin content to be injected into core NetBox templates. It contains methods - that are overridden by plugin authors to return template content. - - The `model` attribute on the class defines the which model detail page this class renders content for. It - should be set as a string in the form '.'. render() provides the following context data: - - * object - The object being viewed - * request - The current request - * settings - Global NetBox settings - * config - Plugin-specific configuration parameters - """ - model = None - - def __init__(self, context): - self.context = context - - def render(self, template_name, extra_context=None): - """ - Convenience method for rendering the specified Django template using the default context data. An additional - context dictionary may be passed as `extra_context`. - """ - if extra_context is None: - extra_context = {} - elif not isinstance(extra_context, dict): - raise TypeError("extra_context must be a dictionary") - - return get_template(template_name).render({**self.context, **extra_context}) - - def left_page(self): - """ - Content that will be rendered on the left of the detail page view. Content should be returned as an - HTML string. Note that content does not need to be marked as safe because this is automatically handled. - """ - raise NotImplementedError - - def right_page(self): - """ - Content that will be rendered on the right of the detail page view. Content should be returned as an - HTML string. Note that content does not need to be marked as safe because this is automatically handled. - """ - raise NotImplementedError - - def full_width_page(self): - """ - Content that will be rendered within the full width of the detail page view. Content should be returned as an - HTML string. Note that content does not need to be marked as safe because this is automatically handled. - """ - raise NotImplementedError - - def buttons(self): - """ - Buttons that will be rendered and added to the existing list of buttons on the detail page view. Content - should be returned as an HTML string. Note that content does not need to be marked as safe because this is - automatically handled. - """ - raise NotImplementedError - - def list_buttons(self): - """ - Buttons that will be rendered and added to the existing list of buttons on the list view. Content - should be returned as an HTML string. Note that content does not need to be marked as safe because this is - automatically handled. - """ - raise NotImplementedError +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/plugins/urls.py b/netbox/extras/plugins/urls.py index 2f237f56a..8b24e8fd2 100644 --- a/netbox/extras/plugins/urls.py +++ b/netbox/extras/plugins/urls.py @@ -1,41 +1,7 @@ -from importlib import import_module +import warnings -from django.apps import apps -from django.conf import settings -from django.conf.urls import include -from django.contrib.admin.views.decorators import staff_member_required -from django.urls import path -from django.utils.module_loading import import_string, module_has_submodule +from netbox.plugins.urls import * -from . import views -# Initialize URL base, API, and admin URL patterns for plugins -plugin_patterns = [] -plugin_api_patterns = [ - path('', views.PluginsAPIRootView.as_view(), name='api-root'), - path('installed-plugins/', views.InstalledPluginsAPIView.as_view(), name='plugins-list') -] -plugin_admin_patterns = [ - path('installed-plugins/', staff_member_required(views.InstalledPluginsAdminView.as_view()), name='plugins_list') -] - -# Register base/API URL patterns for each plugin -for plugin_path in settings.PLUGINS: - plugin = import_module(plugin_path) - plugin_name = plugin_path.split('.')[-1] - app = apps.get_app_config(plugin_name) - base_url = getattr(app, 'base_url') or app.label - - # Check if the plugin specifies any base URLs - if module_has_submodule(plugin, 'urls'): - urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns") - plugin_patterns.append( - path(f"{base_url}/", include((urlpatterns, app.label))) - ) - - # Check if the plugin specifies any API URLs - if module_has_submodule(plugin, 'api.urls'): - urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns") - plugin_api_patterns.append( - path(f"{base_url}/", include((urlpatterns, f"{app.label}-api"))) - ) +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/plugins/utils.py b/netbox/extras/plugins/utils.py index c260f156d..15ae018d1 100644 --- a/netbox/extras/plugins/utils.py +++ b/netbox/extras/plugins/utils.py @@ -1,37 +1,7 @@ -from django.apps import apps -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured +import warnings -__all__ = ( - 'get_installed_plugins', - 'get_plugin_config', -) +from netbox.plugins.utils import * -def get_installed_plugins(): - """ - Return a dictionary mapping the names of installed plugins to their versions. - """ - plugins = {} - for plugin_name in settings.PLUGINS: - plugin_name = plugin_name.rsplit('.', 1)[-1] - plugin_config = apps.get_app_config(plugin_name) - plugins[plugin_name] = getattr(plugin_config, 'version', None) - - return dict(sorted(plugins.items())) - - -def get_plugin_config(plugin_name, parameter, default=None): - """ - Return the value of the specified plugin configuration parameter. - - Args: - plugin_name: The name of the plugin - parameter: The name of the configuration parameter - default: The value to return if the parameter is not defined (default: None) - """ - try: - plugin_config = settings.PLUGINS_CONFIG[plugin_name] - return plugin_config.get(parameter, default) - except KeyError: - raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.") +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/plugins/views.py b/netbox/extras/plugins/views.py index 5971f78ef..505742e6b 100644 --- a/netbox/extras/plugins/views.py +++ b/netbox/extras/plugins/views.py @@ -1,89 +1,7 @@ -from collections import OrderedDict +import warnings -from django.apps import apps -from django.conf import settings -from django.shortcuts import render -from django.urls.exceptions import NoReverseMatch -from django.views.generic import View -from drf_spectacular.utils import extend_schema -from rest_framework import permissions -from rest_framework.response import Response -from rest_framework.reverse import reverse -from rest_framework.views import APIView +from netbox.plugins.views import * -class InstalledPluginsAdminView(View): - """ - Admin view for listing all installed plugins - """ - def get(self, request): - plugins = [apps.get_app_config(plugin) for plugin in settings.PLUGINS] - return render(request, 'extras/admin/plugins_list.html', { - 'plugins': plugins, - }) - - -@extend_schema(exclude=True) -class InstalledPluginsAPIView(APIView): - """ - API view for listing all installed plugins - """ - permission_classes = [permissions.IsAdminUser] - _ignore_model_permissions = True - schema = None - - def get_view_name(self): - return "Installed Plugins" - - @staticmethod - def _get_plugin_data(plugin_app_config): - return { - 'name': plugin_app_config.verbose_name, - 'package': plugin_app_config.name, - 'author': plugin_app_config.author, - 'author_email': plugin_app_config.author_email, - 'description': plugin_app_config.description, - 'version': plugin_app_config.version - } - - def get(self, request, format=None): - return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS]) - - -@extend_schema(exclude=True) -class PluginsAPIRootView(APIView): - _ignore_model_permissions = True - schema = None - - def get_view_name(self): - return "Plugins" - - @staticmethod - def _get_plugin_entry(plugin, app_config, request, format): - # Check if the plugin specifies any API URLs - api_app_name = f'{app_config.name}-api' - try: - entry = (getattr(app_config, 'base_url', app_config.label), reverse( - f"plugins-api:{api_app_name}:api-root", - request=request, - format=format - )) - except NoReverseMatch: - # The plugin does not include an api-root url - entry = None - - return entry - - def get(self, request, format=None): - - entries = [] - for plugin in settings.PLUGINS: - app_config = apps.get_app_config(plugin) - entry = self._get_plugin_entry(plugin, app_config, request, format) - if entry is not None: - entries.append(entry) - - return Response(OrderedDict(( - ('installed-plugins', reverse('plugins-api:plugins-list', request=request, format=format)), - *entries - ))) +# TODO: Remove in v4.0 +warnings.warn(f"{__name__} is deprecated. Import from netbox.plugins instead.", DeprecationWarning) diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index cc279a49a..c8a13fe15 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -40,8 +40,8 @@ def run_report(job, *args, **kwargs): try: report.run(job) - except Exception: - job.terminate(status=JobStatusChoices.STATUS_ERRORED) + except Exception as e: + job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=str(e)) logging.error(f"Error during execution of report {job.name}") finally: # Schedule the next job if an interval has been set @@ -230,7 +230,7 @@ class Report(object): stacktrace = traceback.format_exc() self.log_failure(None, f"An exception occurred: {type(e).__name__}: {e}
{stacktrace}
") logger.error(f"Exception raised during report execution: {e}") - job.terminate(status=JobStatusChoices.STATUS_ERRORED) + job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=str(e)) # Perform any post-run tasks self.post_run() diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index e93326ddc..df75200e6 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -519,7 +519,7 @@ def run_script(data, request, job, commit=True, **kwargs): logger.error(f"Exception raised during script execution: {e}") script.log_info("Database changes have been reverted due to error.") job.data = ScriptOutputSerializer(script).data - job.terminate(status=JobStatusChoices.STATUS_ERRORED) + job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=str(e)) clear_webhooks.send(request) logger.info(f"Script completed in {job.duration}") diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index d6550309f..8bdaf523c 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -2,8 +2,10 @@ import importlib import logging from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError from django.db.models.signals import m2m_changed, post_save, pre_delete from django.dispatch import receiver, Signal +from django.utils.translation import gettext_lazy as _ from django_prometheus.models import model_deletes, model_inserts, model_updates from extras.validators import CustomValidator @@ -178,11 +180,7 @@ m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_type # Custom validation # -@receiver(post_clean) -def run_custom_validators(sender, instance, **kwargs): - config = get_config() - model_name = f'{sender._meta.app_label}.{sender._meta.model_name}' - validators = config.CUSTOM_VALIDATORS.get(model_name, []) +def run_validators(instance, validators): for validator in validators: @@ -198,6 +196,29 @@ def run_custom_validators(sender, instance, **kwargs): validator(instance) +@receiver(post_clean) +def run_save_validators(sender, instance, **kwargs): + model_name = f'{sender._meta.app_label}.{sender._meta.model_name}' + validators = get_config().CUSTOM_VALIDATORS.get(model_name, []) + + run_validators(instance, validators) + + +@receiver(pre_delete) +def run_delete_validators(sender, instance, **kwargs): + model_name = f'{sender._meta.app_label}.{sender._meta.model_name}' + validators = get_config().PROTECTION_RULES.get(model_name, []) + + try: + run_validators(instance, validators) + except ValidationError as e: + raise AbortRequest( + _("Deletion is prevented by a protection rule: {message}").format( + message=e + ) + ) + + # # Dynamic configuration # diff --git a/netbox/extras/tests/test_customvalidator.py b/netbox/extras/tests/test_customvalidation.py similarity index 64% rename from netbox/extras/tests/test_customvalidator.py rename to netbox/extras/tests/test_customvalidation.py index 0fe507b67..d74ad599b 100644 --- a/netbox/extras/tests/test_customvalidator.py +++ b/netbox/extras/tests/test_customvalidation.py @@ -1,10 +1,13 @@ from django.conf import settings from django.core.exceptions import ValidationError +from django.db import transaction from django.test import TestCase, override_settings from ipam.models import ASN, RIR +from dcim.choices import SiteStatusChoices from dcim.models import Site from extras.validators import CustomValidator +from utilities.exceptions import AbortRequest class MyValidator(CustomValidator): @@ -14,6 +17,20 @@ class MyValidator(CustomValidator): self.fail("Name must be foo!") +eq_validator = CustomValidator({ + 'asn': { + 'eq': 100 + } +}) + + +neq_validator = CustomValidator({ + 'asn': { + 'neq': 100 + } +}) + + min_validator = CustomValidator({ 'asn': { 'min': 65000 @@ -77,6 +94,18 @@ class CustomValidatorTest(TestCase): validator = settings.CUSTOM_VALIDATORS['ipam.asn'][0] self.assertIsInstance(validator, CustomValidator) + @override_settings(CUSTOM_VALIDATORS={'ipam.asn': [eq_validator]}) + def test_eq(self): + ASN(asn=100, rir=RIR.objects.first()).clean() + with self.assertRaises(ValidationError): + ASN(asn=99, rir=RIR.objects.first()).clean() + + @override_settings(CUSTOM_VALIDATORS={'ipam.asn': [neq_validator]}) + def test_neq(self): + ASN(asn=99, rir=RIR.objects.first()).clean() + with self.assertRaises(ValidationError): + ASN(asn=100, rir=RIR.objects.first()).clean() + @override_settings(CUSTOM_VALIDATORS={'ipam.asn': [min_validator]}) def test_min(self): with self.assertRaises(ValidationError): @@ -147,7 +176,7 @@ class CustomValidatorConfigTest(TestCase): @override_settings( CUSTOM_VALIDATORS={ 'dcim.site': ( - 'extras.tests.test_customvalidator.MyValidator', + 'extras.tests.test_customvalidation.MyValidator', ) } ) @@ -159,3 +188,62 @@ class CustomValidatorConfigTest(TestCase): Site(name='foo', slug='foo').clean() with self.assertRaises(ValidationError): Site(name='bar', slug='bar').clean() + + +class ProtectionRulesConfigTest(TestCase): + + @override_settings( + PROTECTION_RULES={ + 'dcim.site': [ + {'status': {'eq': SiteStatusChoices.STATUS_DECOMMISSIONING}} + ] + } + ) + def test_plain_data(self): + """ + Test custom validator configuration using plain data (as opposed to a CustomValidator + class) + """ + # Create a site with a protected status + site = Site(name='Site 1', slug='site-1', status=SiteStatusChoices.STATUS_ACTIVE) + site.save() + + # Try to delete it + with self.assertRaises(AbortRequest): + with transaction.atomic(): + site.delete() + + # Change its status to an allowed value + site.status = SiteStatusChoices.STATUS_DECOMMISSIONING + site.save() + + # Deletion should now succeed + site.delete() + + @override_settings( + PROTECTION_RULES={ + 'dcim.site': ( + 'extras.tests.test_customvalidation.MyValidator', + ) + } + ) + def test_dotted_path(self): + """ + Test custom validator configuration using a dotted path (string) reference to a + CustomValidator class. + """ + # Create a site with a protected name + site = Site(name='bar', slug='bar') + site.save() + + # Try to delete it + with self.assertRaises(AbortRequest): + with transaction.atomic(): + site.delete() + + # Change the name to an allowed value + site.name = site.slug = 'foo' + site.save() + + # Deletion should now succeed + site.delete() diff --git a/netbox/extras/validators.py b/netbox/extras/validators.py index 686c9b032..98b4fd88d 100644 --- a/netbox/extras/validators.py +++ b/netbox/extras/validators.py @@ -1,15 +1,38 @@ -from django.core.exceptions import ValidationError from django.core import validators +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ # NOTE: As this module may be imported by configuration.py, we cannot import # anything from NetBox itself. +class IsEqualValidator(validators.BaseValidator): + """ + Employed by CustomValidator to require a specific value. + """ + message = _("Ensure this value is equal to %(limit_value)s.") + code = "is_equal" + + def compare(self, a, b): + return a != b + + +class IsNotEqualValidator(validators.BaseValidator): + """ + Employed by CustomValidator to exclude a specific value. + """ + message = _("Ensure this value does not equal %(limit_value)s.") + code = "is_not_equal" + + def compare(self, a, b): + return a == b + + class IsEmptyValidator: """ Employed by CustomValidator to enforce required fields. """ - message = "This field must be empty." + message = _("This field must be empty.") code = 'is_empty' def __init__(self, enforce=True): @@ -24,7 +47,7 @@ class IsNotEmptyValidator: """ Employed by CustomValidator to enforce prohibited fields. """ - message = "This field must not be empty." + message = _("This field must not be empty.") code = 'not_empty' def __init__(self, enforce=True): @@ -50,6 +73,8 @@ class CustomValidator: :param validation_rules: A dictionary mapping object attributes to validation rules """ VALIDATORS = { + 'eq': IsEqualValidator, + 'neq': IsNotEqualValidator, 'min': validators.MinValueValidator, 'max': validators.MaxValueValidator, 'min_length': validators.MinLengthValidator, diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 55b73d29d..0e8e3b0ea 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -16,6 +16,7 @@ from core.tables import JobTable from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.utils import get_widget_class from netbox.config import get_config, PARAMS +from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.views import generic from utilities.forms import ConfirmationForm, get_field_value from utilities.htmx import is_htmx @@ -210,7 +211,10 @@ class ExportTemplateListView(generic.ObjectListView): filterset_form = forms.ExportTemplateFilterForm table = tables.ExportTemplateTable template_name = 'extras/exporttemplate_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_sync') + actions = { + **DEFAULT_ACTION_PERMISSIONS, + 'bulk_sync': {'sync'}, + } @register_model_view(ExportTemplate) @@ -472,7 +476,12 @@ class ConfigContextListView(generic.ObjectListView): filterset_form = forms.ConfigContextFilterForm table = tables.ConfigContextTable template_name = 'extras/configcontext_list.html' - actions = ('add', 'bulk_edit', 'bulk_delete', 'bulk_sync') + actions = { + 'add': {'add'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + 'bulk_sync': {'sync'}, + } @register_model_view(ConfigContext) @@ -576,7 +585,10 @@ class ConfigTemplateListView(generic.ObjectListView): filterset_form = forms.ConfigTemplateFilterForm table = tables.ConfigTemplateTable template_name = 'extras/configtemplate_list.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_sync') + actions = { + **DEFAULT_ACTION_PERMISSIONS, + 'bulk_sync': {'sync'}, + } @register_model_view(ConfigTemplate) @@ -627,7 +639,9 @@ class ObjectChangeListView(generic.ObjectListView): filterset_form = forms.ObjectChangeFilterForm table = tables.ObjectChangeTable template_name = 'extras/objectchange_list.html' - actions = ('export',) + actions = { + 'export': {'view'}, + } @register_model_view(ObjectChange) @@ -693,7 +707,9 @@ class ImageAttachmentListView(generic.ObjectListView): filterset = filtersets.ImageAttachmentFilterSet filterset_form = forms.ImageAttachmentFilterForm table = tables.ImageAttachmentTable - actions = ('export',) + actions = { + 'export': {'view'}, + } @register_model_view(ImageAttachment, 'edit') @@ -736,7 +752,12 @@ class JournalEntryListView(generic.ObjectListView): filterset = filtersets.JournalEntryFilterSet filterset_form = forms.JournalEntryFilterForm table = tables.JournalEntryTable - actions = ('import', 'export', 'bulk_edit', 'bulk_delete') + actions = { + 'import': {'add'}, + 'export': {'view'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + } @register_model_view(JournalEntry) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index bfd4f952d..dd9e6b3e4 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -372,14 +372,14 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): # Do not allow assigning a network ID or broadcast address to an interface. if interface and (address := self.cleaned_data.get('address')): if address.ip == address.network: - msg = _("{address} is a network ID, which may not be assigned to an interface.").format(address=address) + msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(ip=address.ip) if address.version == 4 and address.prefixlen not in (31, 32): raise ValidationError(msg) if address.version == 6 and address.prefixlen not in (127, 128): raise ValidationError(msg) if address.version == 4 and address.ip == address.broadcast and address.prefixlen not in (31, 32): - msg = _("{address} is a broadcast address, which may not be assigned to an interface.").format( - address=address + msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format( + ip=address.ip ) raise ValidationError(msg) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index d176d3bff..934cb98c7 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -140,8 +140,11 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel): if covering_aggregates: raise ValidationError({ 'prefix': _( - "Aggregates cannot overlap. {} is already covered by an existing aggregate ({})." - ).format(self.prefix, covering_aggregates[0]) + "Aggregates cannot overlap. {prefix} is already covered by an existing aggregate ({aggregate})." + ).format( + prefix=self.prefix, + aggregate=covering_aggregates[0] + ) }) # Ensure that the aggregate being added does not cover an existing aggregate @@ -150,8 +153,11 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel): covered_aggregates = covered_aggregates.exclude(pk=self.pk) if covered_aggregates: raise ValidationError({ - 'prefix': _("Aggregates cannot overlap. {} covers an existing aggregate ({}).").format( - self.prefix, covered_aggregates[0] + 'prefix': _( + "Prefixes cannot overlap aggregates. {prefix} covers an existing aggregate ({aggregate})." + ).format( + prefix=self.prefix, + aggregate=covered_aggregates[0] ) }) @@ -314,10 +320,11 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel): if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique): duplicate_prefixes = self.get_duplicates() if duplicate_prefixes: + table = _("VRF {vrf}").format(vrf=self.vrf) if self.vrf else _("global table") raise ValidationError({ - 'prefix': _("Duplicate prefix found in {}: {}").format( - _("VRF {}").format(self.vrf) if self.vrf else _("global table"), - duplicate_prefixes.first(), + 'prefix': _("Duplicate prefix found in {table}: {prefix}").format( + table=table, + prefix=duplicate_prefixes.first(), ) }) @@ -843,10 +850,11 @@ class IPAddress(PrimaryModel): self.role not in IPADDRESS_ROLES_NONUNIQUE or any(dip.role not in IPADDRESS_ROLES_NONUNIQUE for dip in duplicate_ips) ): + table = _("VRF {vrf}").format(vrf=self.vrf) if self.vrf else _("global table") raise ValidationError({ - 'address': _("Duplicate IP address found in {}: {}").format( - _("VRF {}").format(self.vrf) if self.vrf else _("global table"), - duplicate_ips.first(), + 'address': _("Duplicate IP address found in {table}: {ipaddress}").format( + table=table, + ipaddress=duplicate_ips.first(), ) }) diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index aa5b36a57..675d03ee5 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -234,8 +234,8 @@ class VLAN(PrimaryModel): if self.group and not self.group.min_vid <= self.vid <= self.group.max_vid: raise ValidationError({ 'vid': _( - "VID must be between {min_vid} and {max_vid} for VLANs in group {group}" - ).format(min_vid=self.group.min_vid, max_vid=self.group.max_vid, group=self.group) + "VID must be between {minimum} and {maximum} for VLANs in group {group}" + ).format(minimum=self.group.min_vid, maximum=self.group.max_vid, group=self.group) }) def get_status_color(self): diff --git a/netbox/netbox/api/views.py b/netbox/netbox/api/views.py index 97f690762..4e71ca193 100644 --- a/netbox/netbox/api/views.py +++ b/netbox/netbox/api/views.py @@ -11,7 +11,7 @@ from rest_framework.reverse import reverse from rest_framework.views import APIView from rq.worker import Worker -from extras.plugins.utils import get_installed_plugins +from netbox.plugins.utils import get_installed_plugins from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired diff --git a/netbox/netbox/config/parameters.py b/netbox/netbox/config/parameters.py index 31c4f693a..0cdf8a8d2 100644 --- a/netbox/netbox/config/parameters.py +++ b/netbox/netbox/config/parameters.py @@ -152,9 +152,17 @@ PARAMS = ( description=_("Custom validation rules (JSON)"), field=forms.JSONField, field_kwargs={ - 'widget': forms.Textarea( - attrs={'class': 'vLargeTextField'} - ), + 'widget': forms.Textarea(), + }, + ), + ConfigParam( + name='PROTECTION_RULES', + label=_('Protection rules'), + default={}, + description=_("Deletion protection rules (JSON)"), + field=forms.JSONField, + field_kwargs={ + 'widget': forms.Textarea(), }, ), diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index 18a3c2afa..cec05cabb 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -15,7 +15,7 @@ DATABASE = { } PLUGINS = [ - 'extras.tests.dummy_plugin', + 'netbox.tests.dummy_plugin', ] REDIS = { diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index 2f4ee8e6b..faddf8c21 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -27,3 +27,12 @@ ADVISORY_LOCK_KEYS = { 'inventoryitem': 105700, 'inventoryitemtemplate': 105800, } + +# Default view action permission mapping +DEFAULT_ACTION_PERMISSIONS = { + 'add': {'add'}, + 'import': {'add'}, + 'export': {'view'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, +} diff --git a/netbox/netbox/data_backends.py b/netbox/netbox/data_backends.py new file mode 100644 index 000000000..d5bab75c1 --- /dev/null +++ b/netbox/netbox/data_backends.py @@ -0,0 +1,53 @@ +from contextlib import contextmanager +from urllib.parse import urlparse + +__all__ = ( + 'DataBackend', +) + + +class DataBackend: + """ + A data backend represents a specific system of record for data, such as a git repository or Amazon S3 bucket. + + Attributes: + name: The identifier under which this backend will be registered in NetBox + label: The human-friendly name for this backend + is_local: A boolean indicating whether this backend accesses local data + parameters: A dictionary mapping configuration form field names to their classes + sensitive_parameters: An iterable of field names for which the values should not be displayed to the user + """ + is_local = False + parameters = {} + sensitive_parameters = [] + + # Prevent Django's template engine from calling the backend + # class when referenced via DataSource.backend_class + do_not_call_in_templates = True + + def __init__(self, url, **kwargs): + self.url = url + self.params = kwargs + self.config = self.init_config() + + def init_config(self): + """ + A hook to initialize the instance's configuration. The data returned by this method is assigned to the + instance's `config` attribute upon initialization, which can be referenced by the `fetch()` method. + """ + return + + @property + def url_scheme(self): + return urlparse(self.url).scheme.lower() + + @contextmanager + def fetch(self): + """ + A context manager which performs the following: + + 1. Handles all setup and synchronization + 2. Yields the local path at which data has been replicated + 3. Performs any necessary cleanup + """ + raise NotImplemented() diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 5b64cfc1e..961fd2035 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -1,4 +1,4 @@ -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from netbox.registry import registry from utilities.choices import ButtonColorChoices diff --git a/netbox/netbox/plugins/__init__.py b/netbox/netbox/plugins/__init__.py new file mode 100644 index 000000000..8b6901b7a --- /dev/null +++ b/netbox/netbox/plugins/__init__.py @@ -0,0 +1,156 @@ +import collections +from importlib import import_module + +from django.apps import AppConfig +from django.core.exceptions import ImproperlyConfigured +from django.utils.module_loading import import_string +from packaging import version + +from netbox.registry import registry +from netbox.search import register_search +from netbox.utils import register_data_backend +from .navigation import * +from .registration import * +from .templates import * +from .utils import * + +# Initialize plugin registry +registry['plugins'].update({ + 'graphql_schemas': [], + 'menus': [], + 'menu_items': {}, + 'preferences': {}, + 'template_extensions': collections.defaultdict(list), +}) + +DEFAULT_RESOURCE_PATHS = { + 'search_indexes': 'search.indexes', + 'data_backends': 'data_backends.backends', + 'graphql_schema': 'graphql.schema', + 'menu': 'navigation.menu', + 'menu_items': 'navigation.menu_items', + 'template_extensions': 'template_content.template_extensions', + 'user_preferences': 'preferences.preferences', +} + + +# +# Plugin AppConfig class +# + +class PluginConfig(AppConfig): + """ + Subclass of Django's built-in AppConfig class, to be used for NetBox plugins. + """ + # Plugin metadata + author = '' + author_email = '' + description = '' + version = '' + + # Root URL path under /plugins. If not set, the plugin's label will be used. + base_url = None + + # Minimum/maximum compatible versions of NetBox + min_version = None + max_version = None + + # Default configuration parameters + default_settings = {} + + # Mandatory configuration parameters + required_settings = [] + + # Middleware classes provided by the plugin + middleware = [] + + # Django-rq queues dedicated to the plugin + queues = [] + + # Django apps to append to INSTALLED_APPS when plugin requires them. + django_apps = [] + + # Optional plugin resources + search_indexes = None + data_backends = None + graphql_schema = None + menu = None + menu_items = None + template_extensions = None + user_preferences = None + + def _load_resource(self, name): + # Import from the configured path, if defined. + if path := getattr(self, name, None): + return import_string(f"{self.__module__}.{path}") + + # Fall back to the resource's default path. Return None if the module has not been provided. + default_path = f'{self.__module__}.{DEFAULT_RESOURCE_PATHS[name]}' + default_module, resource_name = default_path.rsplit('.', 1) + try: + module = import_module(default_module) + return getattr(module, resource_name, None) + except ModuleNotFoundError: + pass + + def ready(self): + plugin_name = self.name.rsplit('.', 1)[-1] + + # Register search extensions (if defined) + search_indexes = self._load_resource('search_indexes') or [] + for idx in search_indexes: + register_search(idx) + + # Register data backends (if defined) + data_backends = self._load_resource('data_backends') or [] + for backend in data_backends: + register_data_backend()(backend) + + # Register template content (if defined) + if template_extensions := self._load_resource('template_extensions'): + register_template_extensions(template_extensions) + + # Register navigation menu and/or menu items (if defined) + if menu := self._load_resource('menu'): + register_menu(menu) + if menu_items := self._load_resource('menu_items'): + register_menu_items(self.verbose_name, menu_items) + + # Register GraphQL schema (if defined) + if graphql_schema := self._load_resource('graphql_schema'): + register_graphql_schema(graphql_schema) + + # Register user preferences (if defined) + if user_preferences := self._load_resource('user_preferences'): + register_user_preferences(plugin_name, user_preferences) + + @classmethod + def validate(cls, user_config, netbox_version): + + # Enforce version constraints + current_version = version.parse(netbox_version) + if cls.min_version is not None: + min_version = version.parse(cls.min_version) + if current_version < min_version: + raise ImproperlyConfigured( + f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}." + ) + if cls.max_version is not None: + max_version = version.parse(cls.max_version) + if current_version > max_version: + raise ImproperlyConfigured( + f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}." + ) + + # Verify required configuration settings + for setting in cls.required_settings: + if setting not in user_config: + raise ImproperlyConfigured( + f"Plugin {cls.__module__} requires '{setting}' to be present in the PLUGINS_CONFIG section of " + f"configuration.py." + ) + + # Apply default configuration values + for setting, value in cls.default_settings.items(): + if setting not in user_config: + user_config[setting] = value diff --git a/netbox/netbox/plugins/navigation.py b/netbox/netbox/plugins/navigation.py new file mode 100644 index 000000000..2075c97b6 --- /dev/null +++ b/netbox/netbox/plugins/navigation.py @@ -0,0 +1,72 @@ +from netbox.navigation import MenuGroup +from utilities.choices import ButtonColorChoices +from django.utils.text import slugify + +__all__ = ( + 'PluginMenu', + 'PluginMenuButton', + 'PluginMenuItem', +) + + +class PluginMenu: + icon_class = 'mdi mdi-puzzle' + + def __init__(self, label, groups, icon_class=None): + self.label = label + self.groups = [ + MenuGroup(label, items) for label, items in groups + ] + if icon_class is not None: + self.icon_class = icon_class + + @property + def name(self): + return slugify(self.label) + + +class PluginMenuItem: + """ + This class represents a navigation menu item. This constitutes primary link and its text, but also allows for + specifying additional link buttons that appear to the right of the item in the van menu. + + Links are specified as Django reverse URL strings. + Buttons are each specified as a list of PluginMenuButton instances. + """ + permissions = [] + buttons = [] + + def __init__(self, link, link_text, staff_only=False, permissions=None, buttons=None): + self.link = link + self.link_text = link_text + self.staff_only = staff_only + if permissions is not None: + if type(permissions) not in (list, tuple): + raise TypeError("Permissions must be passed as a tuple or list.") + self.permissions = permissions + if buttons is not None: + if type(buttons) not in (list, tuple): + raise TypeError("Buttons must be passed as a tuple or list.") + self.buttons = buttons + + +class PluginMenuButton: + """ + This class represents a button within a PluginMenuItem. Note that button colors should come from + ButtonColorChoices. + """ + color = ButtonColorChoices.DEFAULT + permissions = [] + + def __init__(self, link, title, icon_class, color=None, permissions=None): + self.link = link + self.title = title + self.icon_class = icon_class + if permissions is not None: + if type(permissions) not in (list, tuple): + raise TypeError("Permissions must be passed as a tuple or list.") + self.permissions = permissions + if color is not None: + if color not in ButtonColorChoices.values(): + raise ValueError("Button color must be a choice within ButtonColorChoices.") + self.color = color diff --git a/netbox/netbox/plugins/registration.py b/netbox/netbox/plugins/registration.py new file mode 100644 index 000000000..3be538441 --- /dev/null +++ b/netbox/netbox/plugins/registration.py @@ -0,0 +1,64 @@ +import inspect + +from netbox.registry import registry +from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem +from .templates import PluginTemplateExtension + +__all__ = ( + 'register_graphql_schema', + 'register_menu', + 'register_menu_items', + 'register_template_extensions', + 'register_user_preferences', +) + + +def register_template_extensions(class_list): + """ + Register a list of PluginTemplateExtension classes + """ + # Validation + for template_extension in class_list: + if not inspect.isclass(template_extension): + raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!") + if not issubclass(template_extension, PluginTemplateExtension): + raise TypeError(f"{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!") + if template_extension.model is None: + raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!") + + registry['plugins']['template_extensions'][template_extension.model].append(template_extension) + + +def register_menu(menu): + if not isinstance(menu, PluginMenu): + raise TypeError(f"{menu} must be an instance of netbox.plugins.PluginMenu") + registry['plugins']['menus'].append(menu) + + +def register_menu_items(section_name, class_list): + """ + Register a list of PluginMenuItem instances for a given menu section (e.g. plugin name) + """ + # Validation + for menu_link in class_list: + if not isinstance(menu_link, PluginMenuItem): + raise TypeError(f"{menu_link} must be an instance of netbox.plugins.PluginMenuItem") + for button in menu_link.buttons: + if not isinstance(button, PluginMenuButton): + raise TypeError(f"{button} must be an instance of netbox.plugins.PluginMenuButton") + + registry['plugins']['menu_items'][section_name] = class_list + + +def register_graphql_schema(graphql_schema): + """ + Register a GraphQL schema class for inclusion in NetBox's GraphQL API. + """ + registry['plugins']['graphql_schemas'].append(graphql_schema) + + +def register_user_preferences(plugin_name, preferences): + """ + Register a list of user preferences defined by a plugin. + """ + registry['plugins']['preferences'][plugin_name] = preferences diff --git a/netbox/netbox/plugins/templates.py b/netbox/netbox/plugins/templates.py new file mode 100644 index 000000000..e9b9a9dca --- /dev/null +++ b/netbox/netbox/plugins/templates.py @@ -0,0 +1,73 @@ +from django.template.loader import get_template + +__all__ = ( + 'PluginTemplateExtension', +) + + +class PluginTemplateExtension: + """ + This class is used to register plugin content to be injected into core NetBox templates. It contains methods + that are overridden by plugin authors to return template content. + + The `model` attribute on the class defines the which model detail page this class renders content for. It + should be set as a string in the form '.'. render() provides the following context data: + + * object - The object being viewed + * request - The current request + * settings - Global NetBox settings + * config - Plugin-specific configuration parameters + """ + model = None + + def __init__(self, context): + self.context = context + + def render(self, template_name, extra_context=None): + """ + Convenience method for rendering the specified Django template using the default context data. An additional + context dictionary may be passed as `extra_context`. + """ + if extra_context is None: + extra_context = {} + elif not isinstance(extra_context, dict): + raise TypeError("extra_context must be a dictionary") + + return get_template(template_name).render({**self.context, **extra_context}) + + def left_page(self): + """ + Content that will be rendered on the left of the detail page view. Content should be returned as an + HTML string. Note that content does not need to be marked as safe because this is automatically handled. + """ + raise NotImplementedError + + def right_page(self): + """ + Content that will be rendered on the right of the detail page view. Content should be returned as an + HTML string. Note that content does not need to be marked as safe because this is automatically handled. + """ + raise NotImplementedError + + def full_width_page(self): + """ + Content that will be rendered within the full width of the detail page view. Content should be returned as an + HTML string. Note that content does not need to be marked as safe because this is automatically handled. + """ + raise NotImplementedError + + def buttons(self): + """ + Buttons that will be rendered and added to the existing list of buttons on the detail page view. Content + should be returned as an HTML string. Note that content does not need to be marked as safe because this is + automatically handled. + """ + raise NotImplementedError + + def list_buttons(self): + """ + Buttons that will be rendered and added to the existing list of buttons on the list view. Content + should be returned as an HTML string. Note that content does not need to be marked as safe because this is + automatically handled. + """ + raise NotImplementedError diff --git a/netbox/netbox/plugins/urls.py b/netbox/netbox/plugins/urls.py new file mode 100644 index 000000000..2f237f56a --- /dev/null +++ b/netbox/netbox/plugins/urls.py @@ -0,0 +1,41 @@ +from importlib import import_module + +from django.apps import apps +from django.conf import settings +from django.conf.urls import include +from django.contrib.admin.views.decorators import staff_member_required +from django.urls import path +from django.utils.module_loading import import_string, module_has_submodule + +from . import views + +# Initialize URL base, API, and admin URL patterns for plugins +plugin_patterns = [] +plugin_api_patterns = [ + path('', views.PluginsAPIRootView.as_view(), name='api-root'), + path('installed-plugins/', views.InstalledPluginsAPIView.as_view(), name='plugins-list') +] +plugin_admin_patterns = [ + path('installed-plugins/', staff_member_required(views.InstalledPluginsAdminView.as_view()), name='plugins_list') +] + +# Register base/API URL patterns for each plugin +for plugin_path in settings.PLUGINS: + plugin = import_module(plugin_path) + plugin_name = plugin_path.split('.')[-1] + app = apps.get_app_config(plugin_name) + base_url = getattr(app, 'base_url') or app.label + + # Check if the plugin specifies any base URLs + if module_has_submodule(plugin, 'urls'): + urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns") + plugin_patterns.append( + path(f"{base_url}/", include((urlpatterns, app.label))) + ) + + # Check if the plugin specifies any API URLs + if module_has_submodule(plugin, 'api.urls'): + urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns") + plugin_api_patterns.append( + path(f"{base_url}/", include((urlpatterns, f"{app.label}-api"))) + ) diff --git a/netbox/netbox/plugins/utils.py b/netbox/netbox/plugins/utils.py new file mode 100644 index 000000000..c260f156d --- /dev/null +++ b/netbox/netbox/plugins/utils.py @@ -0,0 +1,37 @@ +from django.apps import apps +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +__all__ = ( + 'get_installed_plugins', + 'get_plugin_config', +) + + +def get_installed_plugins(): + """ + Return a dictionary mapping the names of installed plugins to their versions. + """ + plugins = {} + for plugin_name in settings.PLUGINS: + plugin_name = plugin_name.rsplit('.', 1)[-1] + plugin_config = apps.get_app_config(plugin_name) + plugins[plugin_name] = getattr(plugin_config, 'version', None) + + return dict(sorted(plugins.items())) + + +def get_plugin_config(plugin_name, parameter, default=None): + """ + Return the value of the specified plugin configuration parameter. + + Args: + plugin_name: The name of the plugin + parameter: The name of the configuration parameter + default: The value to return if the parameter is not defined (default: None) + """ + try: + plugin_config = settings.PLUGINS_CONFIG[plugin_name] + return plugin_config.get(parameter, default) + except KeyError: + raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.") diff --git a/netbox/netbox/plugins/views.py b/netbox/netbox/plugins/views.py new file mode 100644 index 000000000..5971f78ef --- /dev/null +++ b/netbox/netbox/plugins/views.py @@ -0,0 +1,89 @@ +from collections import OrderedDict + +from django.apps import apps +from django.conf import settings +from django.shortcuts import render +from django.urls.exceptions import NoReverseMatch +from django.views.generic import View +from drf_spectacular.utils import extend_schema +from rest_framework import permissions +from rest_framework.response import Response +from rest_framework.reverse import reverse +from rest_framework.views import APIView + + +class InstalledPluginsAdminView(View): + """ + Admin view for listing all installed plugins + """ + def get(self, request): + plugins = [apps.get_app_config(plugin) for plugin in settings.PLUGINS] + return render(request, 'extras/admin/plugins_list.html', { + 'plugins': plugins, + }) + + +@extend_schema(exclude=True) +class InstalledPluginsAPIView(APIView): + """ + API view for listing all installed plugins + """ + permission_classes = [permissions.IsAdminUser] + _ignore_model_permissions = True + schema = None + + def get_view_name(self): + return "Installed Plugins" + + @staticmethod + def _get_plugin_data(plugin_app_config): + return { + 'name': plugin_app_config.verbose_name, + 'package': plugin_app_config.name, + 'author': plugin_app_config.author, + 'author_email': plugin_app_config.author_email, + 'description': plugin_app_config.description, + 'version': plugin_app_config.version + } + + def get(self, request, format=None): + return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS]) + + +@extend_schema(exclude=True) +class PluginsAPIRootView(APIView): + _ignore_model_permissions = True + schema = None + + def get_view_name(self): + return "Plugins" + + @staticmethod + def _get_plugin_entry(plugin, app_config, request, format): + # Check if the plugin specifies any API URLs + api_app_name = f'{app_config.name}-api' + try: + entry = (getattr(app_config, 'base_url', app_config.label), reverse( + f"plugins-api:{api_app_name}:api-root", + request=request, + format=format + )) + except NoReverseMatch: + # The plugin does not include an api-root url + entry = None + + return entry + + def get(self, request, format=None): + + entries = [] + for plugin in settings.PLUGINS: + app_config = apps.get_app_config(plugin) + entry = self._get_plugin_entry(plugin, app_config, request, format) + if entry is not None: + entries.append(entry) + + return Response(OrderedDict(( + ('installed-plugins', reverse('plugins-api:plugins-list', request=request, format=format)), + *entries + ))) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 111781b8a..4c8b3f960 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -14,11 +14,11 @@ from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import URLValidator from django.utils.encoding import force_str -from extras.plugins import PluginConfig from sentry_sdk.integrations.django import DjangoIntegration from netbox.config import PARAMS from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW +from netbox.plugins import PluginConfig # diff --git a/netbox/extras/tests/dummy_plugin/__init__.py b/netbox/netbox/tests/dummy_plugin/__init__.py similarity index 72% rename from netbox/extras/tests/dummy_plugin/__init__.py rename to netbox/netbox/tests/dummy_plugin/__init__.py index 83baf064f..3ade8f9df 100644 --- a/netbox/extras/tests/dummy_plugin/__init__.py +++ b/netbox/netbox/tests/dummy_plugin/__init__.py @@ -1,8 +1,8 @@ -from extras.plugins import PluginConfig +from netbox.plugins import PluginConfig class DummyPluginConfig(PluginConfig): - name = 'extras.tests.dummy_plugin' + name = 'netbox.tests.dummy_plugin' verbose_name = 'Dummy plugin' version = '0.0' description = 'For testing purposes only' @@ -10,7 +10,7 @@ class DummyPluginConfig(PluginConfig): min_version = '1.0' max_version = '9.0' middleware = [ - 'extras.tests.dummy_plugin.middleware.DummyMiddleware' + 'netbox.tests.dummy_plugin.middleware.DummyMiddleware' ] queues = [ 'testing-low', diff --git a/netbox/extras/tests/dummy_plugin/admin.py b/netbox/netbox/tests/dummy_plugin/admin.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/admin.py rename to netbox/netbox/tests/dummy_plugin/admin.py diff --git a/netbox/extras/tests/dummy_plugin/api/serializers.py b/netbox/netbox/tests/dummy_plugin/api/serializers.py similarity index 76% rename from netbox/extras/tests/dummy_plugin/api/serializers.py rename to netbox/netbox/tests/dummy_plugin/api/serializers.py index 101786168..239d7d998 100644 --- a/netbox/extras/tests/dummy_plugin/api/serializers.py +++ b/netbox/netbox/tests/dummy_plugin/api/serializers.py @@ -1,5 +1,5 @@ from rest_framework.serializers import ModelSerializer -from extras.tests.dummy_plugin.models import DummyModel +from netbox.tests.dummy_plugin.models import DummyModel class DummySerializer(ModelSerializer): diff --git a/netbox/extras/tests/dummy_plugin/api/urls.py b/netbox/netbox/tests/dummy_plugin/api/urls.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/api/urls.py rename to netbox/netbox/tests/dummy_plugin/api/urls.py diff --git a/netbox/extras/tests/dummy_plugin/api/views.py b/netbox/netbox/tests/dummy_plugin/api/views.py similarity index 78% rename from netbox/extras/tests/dummy_plugin/api/views.py rename to netbox/netbox/tests/dummy_plugin/api/views.py index 1977ec2af..58f221285 100644 --- a/netbox/extras/tests/dummy_plugin/api/views.py +++ b/netbox/netbox/tests/dummy_plugin/api/views.py @@ -1,5 +1,5 @@ from rest_framework.viewsets import ModelViewSet -from extras.tests.dummy_plugin.models import DummyModel +from netbox.tests.dummy_plugin.models import DummyModel from .serializers import DummySerializer diff --git a/netbox/netbox/tests/dummy_plugin/data_backends.py b/netbox/netbox/tests/dummy_plugin/data_backends.py new file mode 100644 index 000000000..9b63e51c6 --- /dev/null +++ b/netbox/netbox/tests/dummy_plugin/data_backends.py @@ -0,0 +1,18 @@ +from contextlib import contextmanager + +from netbox.data_backends import DataBackend + + +class DummyBackend(DataBackend): + name = 'dummy' + label = 'Dummy' + is_local = True + + @contextmanager + def fetch(self): + yield '/tmp' + + +backends = ( + DummyBackend, +) diff --git a/netbox/extras/tests/dummy_plugin/graphql.py b/netbox/netbox/tests/dummy_plugin/graphql.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/graphql.py rename to netbox/netbox/tests/dummy_plugin/graphql.py diff --git a/netbox/extras/tests/dummy_plugin/middleware.py b/netbox/netbox/tests/dummy_plugin/middleware.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/middleware.py rename to netbox/netbox/tests/dummy_plugin/middleware.py diff --git a/netbox/extras/tests/dummy_plugin/migrations/0001_initial.py b/netbox/netbox/tests/dummy_plugin/migrations/0001_initial.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/migrations/0001_initial.py rename to netbox/netbox/tests/dummy_plugin/migrations/0001_initial.py diff --git a/netbox/extras/tests/dummy_plugin/migrations/__init__.py b/netbox/netbox/tests/dummy_plugin/migrations/__init__.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/migrations/__init__.py rename to netbox/netbox/tests/dummy_plugin/migrations/__init__.py diff --git a/netbox/extras/tests/dummy_plugin/models.py b/netbox/netbox/tests/dummy_plugin/models.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/models.py rename to netbox/netbox/tests/dummy_plugin/models.py diff --git a/netbox/extras/tests/dummy_plugin/navigation.py b/netbox/netbox/tests/dummy_plugin/navigation.py similarity index 90% rename from netbox/extras/tests/dummy_plugin/navigation.py rename to netbox/netbox/tests/dummy_plugin/navigation.py index a9157b368..4e7bb4be8 100644 --- a/netbox/extras/tests/dummy_plugin/navigation.py +++ b/netbox/netbox/tests/dummy_plugin/navigation.py @@ -1,5 +1,5 @@ from django.utils.translation import gettext as _ -from extras.plugins import PluginMenu, PluginMenuButton, PluginMenuItem +from netbox.plugins.navigation import PluginMenu, PluginMenuButton, PluginMenuItem items = ( diff --git a/netbox/extras/tests/dummy_plugin/preferences.py b/netbox/netbox/tests/dummy_plugin/preferences.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/preferences.py rename to netbox/netbox/tests/dummy_plugin/preferences.py diff --git a/netbox/extras/tests/dummy_plugin/search.py b/netbox/netbox/tests/dummy_plugin/search.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/search.py rename to netbox/netbox/tests/dummy_plugin/search.py diff --git a/netbox/extras/tests/dummy_plugin/template_content.py b/netbox/netbox/tests/dummy_plugin/template_content.py similarity index 88% rename from netbox/extras/tests/dummy_plugin/template_content.py rename to netbox/netbox/tests/dummy_plugin/template_content.py index 364768a22..b63338f2f 100644 --- a/netbox/extras/tests/dummy_plugin/template_content.py +++ b/netbox/netbox/tests/dummy_plugin/template_content.py @@ -1,4 +1,4 @@ -from extras.plugins import PluginTemplateExtension +from netbox.plugins.templates import PluginTemplateExtension class SiteContent(PluginTemplateExtension): diff --git a/netbox/extras/tests/dummy_plugin/urls.py b/netbox/netbox/tests/dummy_plugin/urls.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/urls.py rename to netbox/netbox/tests/dummy_plugin/urls.py diff --git a/netbox/extras/tests/dummy_plugin/views.py b/netbox/netbox/tests/dummy_plugin/views.py similarity index 100% rename from netbox/extras/tests/dummy_plugin/views.py rename to netbox/netbox/tests/dummy_plugin/views.py diff --git a/netbox/extras/tests/test_plugins.py b/netbox/netbox/tests/test_plugins.py similarity index 83% rename from netbox/extras/tests/test_plugins.py rename to netbox/netbox/tests/test_plugins.py index 42dde43fd..046436a86 100644 --- a/netbox/extras/tests/test_plugins.py +++ b/netbox/netbox/tests/test_plugins.py @@ -5,22 +5,23 @@ from django.core.exceptions import ImproperlyConfigured from django.test import Client, TestCase, override_settings from django.urls import reverse -from extras.plugins import PluginMenu -from extras.tests.dummy_plugin import config as dummy_config -from extras.plugins.utils import get_plugin_config +from netbox.tests.dummy_plugin import config as dummy_config +from netbox.tests.dummy_plugin.data_backends import DummyBackend +from netbox.plugins.navigation import PluginMenu +from netbox.plugins.utils import get_plugin_config from netbox.graphql.schema import Query from netbox.registry import registry -@skipIf('extras.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS") +@skipIf('netbox.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS") class PluginTest(TestCase): def test_config(self): - self.assertIn('extras.tests.dummy_plugin.DummyPluginConfig', settings.INSTALLED_APPS) + self.assertIn('netbox.tests.dummy_plugin.DummyPluginConfig', settings.INSTALLED_APPS) def test_models(self): - from extras.tests.dummy_plugin.models import DummyModel + from netbox.tests.dummy_plugin.models import DummyModel # Test saving an instance instance = DummyModel(name='Instance 1', number=100) @@ -92,7 +93,7 @@ class PluginTest(TestCase): """ Check that plugin TemplateExtensions are registered. """ - from extras.tests.dummy_plugin.template_content import SiteContent + from netbox.tests.dummy_plugin.template_content import SiteContent self.assertIn(SiteContent, registry['plugins']['template_extensions']['dcim.site']) @@ -109,15 +110,22 @@ class PluginTest(TestCase): """ Check that plugin middleware is registered. """ - self.assertIn('extras.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE) + self.assertIn('netbox.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE) + + def test_data_backends(self): + """ + Check registered data backends. + """ + self.assertIn('dummy', registry['data_backends']) + self.assertIs(registry['data_backends']['dummy'], DummyBackend) def test_queues(self): """ Check that plugin queues are registered with the accurate name. """ - self.assertIn('extras.tests.dummy_plugin.testing-low', settings.RQ_QUEUES) - self.assertIn('extras.tests.dummy_plugin.testing-medium', settings.RQ_QUEUES) - self.assertIn('extras.tests.dummy_plugin.testing-high', settings.RQ_QUEUES) + self.assertIn('netbox.tests.dummy_plugin.testing-low', settings.RQ_QUEUES) + self.assertIn('netbox.tests.dummy_plugin.testing-medium', settings.RQ_QUEUES) + self.assertIn('netbox.tests.dummy_plugin.testing-high', settings.RQ_QUEUES) def test_min_version(self): """ @@ -170,17 +178,17 @@ class PluginTest(TestCase): """ Validate the registration and operation of plugin-provided GraphQL schemas. """ - from extras.tests.dummy_plugin.graphql import DummyQuery + from netbox.tests.dummy_plugin.graphql import DummyQuery self.assertIn(DummyQuery, registry['plugins']['graphql_schemas']) self.assertTrue(issubclass(Query, DummyQuery)) - @override_settings(PLUGINS_CONFIG={'extras.tests.dummy_plugin': {'foo': 123}}) + @override_settings(PLUGINS_CONFIG={'netbox.tests.dummy_plugin': {'foo': 123}}) def test_get_plugin_config(self): """ Validate that get_plugin_config() returns config parameters correctly. """ - plugin = 'extras.tests.dummy_plugin' + plugin = 'netbox.tests.dummy_plugin' self.assertEqual(get_plugin_config(plugin, 'foo'), 123) self.assertEqual(get_plugin_config(plugin, 'bar'), None) self.assertEqual(get_plugin_config(plugin, 'bar', default=456), 456) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 595a9001f..6955426a8 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -6,10 +6,10 @@ from django.views.static import serve from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from account.views import LoginView, LogoutView -from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns from netbox.api.views import APIRootView, StatusView from netbox.graphql.schema import schema from netbox.graphql.views import GraphQLView +from netbox.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx from .admin import admin_site diff --git a/netbox/netbox/utils.py b/netbox/netbox/utils.py new file mode 100644 index 000000000..f27d1b5f7 --- /dev/null +++ b/netbox/netbox/utils.py @@ -0,0 +1,26 @@ +from netbox.registry import registry + +__all__ = ( + 'get_data_backend_choices', + 'register_data_backend', +) + + +def get_data_backend_choices(): + return [ + (None, '---------'), + *[ + (name, cls.label) for name, cls in registry['data_backends'].items() + ] + ] + + +def register_data_backend(): + """ + Decorator for registering a DataBackend class. + """ + def _wrapper(cls): + registry['data_backends'][cls.name] = cls + return cls + + return _wrapper diff --git a/netbox/netbox/views/errors.py b/netbox/netbox/views/errors.py index a81d45cb5..d1a8ccd36 100644 --- a/netbox/netbox/views/errors.py +++ b/netbox/netbox/views/errors.py @@ -11,7 +11,7 @@ from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found from django.views.generic import View from sentry_sdk import capture_message -from extras.plugins.utils import get_installed_plugins +from netbox.plugins.utils import get_installed_plugins __all__ = ( 'handler_404', diff --git a/netbox/netbox/views/generic/mixins.py b/netbox/netbox/views/generic/mixins.py index a55f01509..d01c534bb 100644 --- a/netbox/netbox/views/generic/mixins.py +++ b/netbox/netbox/views/generic/mixins.py @@ -1,5 +1,6 @@ -from collections import defaultdict +import warnings +from netbox.constants import DEFAULT_ACTION_PERMISSIONS from utilities.permissions import get_permission_for_model __all__ = ( @@ -9,13 +10,15 @@ __all__ = ( class ActionsMixin: - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, - }) + """ + Maps action names to the set of required permissions for each. Object list views reference this mapping to + determine whether to render the applicable button for each action: The button will be rendered only if the user + possesses the specified permission(s). + + Standard actions include: add, import, export, bulk_edit, and bulk_delete. Some views extend this default map + with custom actions, such as bulk_sync. + """ + actions = DEFAULT_ACTION_PERMISSIONS def get_permitted_actions(self, user, model=None): """ @@ -23,11 +26,43 @@ class ActionsMixin: """ model = model or self.queryset.model - return [ - action for action in self.actions if user.has_perms([ - get_permission_for_model(model, name) for name in self.action_perms[action] - ]) - ] + # TODO: Remove backward compatibility in Netbox v4.0 + # Determine how permissions are being mapped to actions for the view + if hasattr(self, 'action_perms'): + # Backward compatibility for <3.7 + permissions_map = self.action_perms + warnings.warn( + "Setting action_perms on views is deprecated and will be removed in NetBox v4.0. Use actions instead.", + DeprecationWarning + ) + elif type(self.actions) is dict: + # New actions format (3.7+) + permissions_map = self.actions + else: + # actions is still defined as a list or tuple (<3.7) but no custom mapping is defined; use the old + # default mapping + permissions_map = { + 'add': {'add'}, + 'import': {'add'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + } + warnings.warn( + "View actions should be defined as a dictionary mapping. Support for the legacy list format will be " + "removed in NetBox v4.0.", + DeprecationWarning + ) + + # Resolve required permissions for each action + permitted_actions = [] + for action in self.actions: + required_permissions = [ + get_permission_for_model(model, name) for name in permissions_map.get(action, set()) + ] + if not required_permissions or user.has_perms(required_permissions): + permitted_actions.append(action) + + return permitted_actions class TableMixin: diff --git a/netbox/templates/circuits/circuittype.html b/netbox/templates/circuits/circuittype.html index b8b08baf0..407ee4042 100644 --- a/netbox/templates/circuits/circuittype.html +++ b/netbox/templates/circuits/circuittype.html @@ -29,6 +29,16 @@ {% trans "Description" %} {{ object.description|placeholder }} + + {% trans "Color" %} + + {% if object.color %} +   + {% else %} + {{ ''|placeholder }} + {% endif %} + + diff --git a/netbox/templates/core/datasource.html b/netbox/templates/core/datasource.html index 369c395f8..51090b0c9 100644 --- a/netbox/templates/core/datasource.html +++ b/netbox/templates/core/datasource.html @@ -58,7 +58,7 @@ {% trans "URL" %} - {% if not object.is_local %} + {% if not object.type.is_local %} {{ object.source_url }} {% else %} {{ object.source_url }} diff --git a/netbox/templates/core/job.html b/netbox/templates/core/job.html index 1fe3862cd..deb651739 100644 --- a/netbox/templates/core/job.html +++ b/netbox/templates/core/job.html @@ -35,6 +35,12 @@ {% trans "Status" %} {% badge object.get_status_display object.get_status_color %} + {% if object.error %} + + {% trans "Error" %} + {{ object.error }} + + {% endif %} {% trans "Created By" %} {{ object.user|placeholder }} diff --git a/netbox/templates/dcim/devicebay_delete.html b/netbox/templates/dcim/devicebay_delete.html index 18f4f6576..9e54baa86 100644 --- a/netbox/templates/dcim/devicebay_delete.html +++ b/netbox/templates/dcim/devicebay_delete.html @@ -8,8 +8,8 @@ {% block message %}

- {% blocktrans trimmed %} - Are you sure you want to delete this device bay from {{ devicebay.device }}? + {% blocktrans trimmed with device=devicebay.device %} + Are you sure you want to delete this device bay from {{ device }}? {% endblocktrans %}

{% endblock %} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 419ab7f00..35b089664 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -40,6 +40,10 @@ {% trans "Height (U" %}) {{ object.u_height|floatformat }} + + {% trans "Exclude From Utilization" %}) + {% checkmark object.exclude_from_utilization %} + {% trans "Full Depth" %} {% checkmark object.is_full_depth %} diff --git a/netbox/templates/extras/configrevision.html b/netbox/templates/extras/configrevision.html index 4f2abf30b..a880865c3 100644 --- a/netbox/templates/extras/configrevision.html +++ b/netbox/templates/extras/configrevision.html @@ -151,6 +151,10 @@ {% trans "Custom validators" %} {{ object.data.CUSTOM_VALIDATORS|placeholder }} + + {% trans "Protection rules" %} + {{ object.data.PROTECTION_RULES|placeholder }} + diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 76a86146c..55193a9a7 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -386,7 +386,11 @@ class ContactAssignmentListView(generic.ObjectListView): filterset = filtersets.ContactAssignmentFilterSet filterset_form = forms.ContactAssignmentFilterForm table = tables.ContactAssignmentTable - actions = ('export', 'bulk_edit', 'bulk_delete') + actions = { + 'export': {'view'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + } @register_model_view(ContactAssignment, 'edit') diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po new file mode 100644 index 000000000..b04e843f2 --- /dev/null +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -0,0 +1,12322 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-30 17:19+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: account/tables.py:27 templates/account/token.html:23 +#: templates/users/token.html:18 users/forms/bulk_import.py:41 +#: users/forms/model_forms.py:113 +msgid "Key" +msgstr "" + +#: account/tables.py:31 users/forms/filtersets.py:135 +msgid "Write Enabled" +msgstr "" + +#: account/tables.py:34 core/tables/jobs.py:28 extras/choices.py:124 +#: extras/tables/tables.py:469 templates/account/token.html:44 +#: templates/core/job.html:52 templates/extras/configrevision.html:34 +#: templates/extras/configrevision_restore.html:12 +#: templates/extras/htmx/report_result.html:11 +#: templates/extras/htmx/script_result.html:12 +#: templates/extras/journalentry.html:25 templates/generic/object.html:48 +#: templates/users/token.html:36 +msgid "Created" +msgstr "" + +#: account/tables.py:37 templates/account/token.html:48 +#: templates/users/token.html:40 users/forms/bulk_edit.py:97 +#: users/forms/filtersets.py:139 +msgid "Expires" +msgstr "" + +#: account/tables.py:40 users/forms/filtersets.py:144 +msgid "Last Used" +msgstr "" + +#: account/tables.py:43 templates/account/token.html:56 +#: templates/users/token.html:48 users/forms/bulk_edit.py:102 +#: users/forms/model_forms.py:125 +msgid "Allowed IPs" +msgstr "" + +#: circuits/choices.py:21 dcim/choices.py:20 dcim/choices.py:102 +#: dcim/choices.py:174 dcim/choices.py:220 dcim/choices.py:1419 +#: dcim/choices.py:1495 dcim/choices.py:1545 virtualization/choices.py:20 +#: virtualization/choices.py:45 +msgid "Planned" +msgstr "" + +#: circuits/choices.py:22 netbox/navigation/menu.py:271 +msgid "Provisioning" +msgstr "" + +#: circuits/choices.py:23 dcim/choices.py:22 dcim/choices.py:103 +#: dcim/choices.py:173 dcim/choices.py:219 dcim/choices.py:1494 +#: dcim/choices.py:1544 extras/tables/tables.py:375 ipam/choices.py:31 +#: ipam/choices.py:49 ipam/choices.py:69 ipam/choices.py:154 +#: templates/extras/configcontext.html:26 templates/users/user.html:34 +#: users/forms/bulk_edit.py:36 virtualization/choices.py:22 +#: virtualization/choices.py:44 wireless/choices.py:25 +msgid "Active" +msgstr "" + +#: circuits/choices.py:24 dcim/choices.py:172 dcim/choices.py:218 +#: dcim/choices.py:1493 dcim/choices.py:1546 virtualization/choices.py:24 +#: virtualization/choices.py:43 +msgid "Offline" +msgstr "" + +#: circuits/choices.py:25 +msgid "Deprovisioning" +msgstr "" + +#: circuits/choices.py:26 +msgid "Decommissioned" +msgstr "" + +#: circuits/filtersets.py:29 circuits/filtersets.py:182 dcim/filtersets.py:118 +#: dcim/filtersets.py:179 dcim/filtersets.py:254 dcim/filtersets.py:362 +#: dcim/filtersets.py:873 dcim/filtersets.py:1179 dcim/filtersets.py:1674 +#: dcim/filtersets.py:1847 dcim/filtersets.py:1904 ipam/filtersets.py:304 +#: ipam/filtersets.py:891 ipam/filtersets.py:1122 +#: virtualization/filtersets.py:43 virtualization/filtersets.py:169 +msgid "Region (ID)" +msgstr "" + +#: circuits/filtersets.py:36 circuits/filtersets.py:189 dcim/filtersets.py:124 +#: dcim/filtersets.py:186 dcim/filtersets.py:261 dcim/filtersets.py:369 +#: dcim/filtersets.py:880 dcim/filtersets.py:1186 dcim/filtersets.py:1681 +#: dcim/filtersets.py:1854 dcim/filtersets.py:1911 extras/filtersets.py:383 +#: ipam/filtersets.py:311 ipam/filtersets.py:898 ipam/filtersets.py:1117 +#: virtualization/filtersets.py:50 virtualization/filtersets.py:176 +msgid "Region (slug)" +msgstr "" + +#: circuits/filtersets.py:42 circuits/filtersets.py:195 dcim/filtersets.py:192 +#: dcim/filtersets.py:267 dcim/filtersets.py:375 dcim/filtersets.py:886 +#: dcim/filtersets.py:1192 dcim/filtersets.py:1687 dcim/filtersets.py:1860 +#: dcim/filtersets.py:1917 ipam/filtersets.py:317 ipam/filtersets.py:904 +#: virtualization/filtersets.py:56 virtualization/filtersets.py:182 +msgid "Site group (ID)" +msgstr "" + +#: circuits/filtersets.py:49 circuits/filtersets.py:202 dcim/filtersets.py:199 +#: dcim/filtersets.py:274 dcim/filtersets.py:382 dcim/filtersets.py:893 +#: dcim/filtersets.py:1199 dcim/filtersets.py:1694 dcim/filtersets.py:1867 +#: dcim/filtersets.py:1924 extras/filtersets.py:389 ipam/filtersets.py:324 +#: ipam/filtersets.py:911 virtualization/filtersets.py:63 +#: virtualization/filtersets.py:189 +msgid "Site group (slug)" +msgstr "" + +#: circuits/filtersets.py:54 circuits/forms/bulk_import.py:117 +#: circuits/forms/filtersets.py:47 circuits/forms/filtersets.py:170 +#: circuits/forms/model_forms.py:137 dcim/forms/bulk_edit.py:166 +#: dcim/forms/bulk_edit.py:238 dcim/forms/bulk_edit.py:570 +#: dcim/forms/bulk_edit.py:763 dcim/forms/bulk_import.py:130 +#: dcim/forms/bulk_import.py:176 dcim/forms/bulk_import.py:249 +#: dcim/forms/bulk_import.py:477 dcim/forms/bulk_import.py:1239 +#: dcim/forms/bulk_import.py:1267 dcim/forms/filtersets.py:83 +#: dcim/forms/filtersets.py:215 dcim/forms/filtersets.py:261 +#: dcim/forms/filtersets.py:370 dcim/forms/filtersets.py:673 +#: dcim/forms/filtersets.py:903 dcim/forms/filtersets.py:927 +#: dcim/forms/filtersets.py:1016 dcim/forms/filtersets.py:1054 +#: dcim/forms/filtersets.py:1459 dcim/forms/filtersets.py:1483 +#: dcim/forms/filtersets.py:1507 dcim/forms/model_forms.py:138 +#: dcim/forms/model_forms.py:167 dcim/forms/model_forms.py:211 +#: dcim/forms/model_forms.py:397 dcim/forms/model_forms.py:629 +#: dcim/forms/object_create.py:357 dcim/tables/devices.py:186 +#: dcim/tables/power.py:26 dcim/tables/racks.py:62 dcim/tables/racks.py:138 +#: dcim/tables/sites.py:129 extras/filtersets.py:399 +#: ipam/forms/bulk_edit.py:217 ipam/forms/bulk_edit.py:271 +#: ipam/forms/bulk_edit.py:449 ipam/forms/bulk_edit.py:521 +#: ipam/forms/bulk_import.py:173 ipam/forms/bulk_import.py:440 +#: ipam/forms/filtersets.py:156 ipam/forms/filtersets.py:230 +#: ipam/forms/filtersets.py:420 ipam/forms/filtersets.py:472 +#: ipam/forms/filtersets.py:585 ipam/forms/model_forms.py:208 +#: ipam/forms/model_forms.py:550 ipam/forms/model_forms.py:642 +#: ipam/tables/ip.py:244 ipam/tables/vlans.py:114 ipam/tables/vlans.py:216 +#: templates/circuits/circuittermination_edit.html:20 +#: templates/circuits/inc/circuit_termination.html:33 +#: templates/dcim/device.html:30 templates/dcim/inc/cable_termination.html:8 +#: templates/dcim/inc/cable_termination.html:33 templates/dcim/location.html:40 +#: templates/dcim/powerpanel.html:23 templates/dcim/rack.html:18 +#: templates/dcim/rackreservation.html:25 templates/dcim/site.html:26 +#: templates/ipam/prefix.html:48 templates/ipam/vlan.html:17 +#: templates/ipam/vlan_edit.html:40 templates/virtualization/cluster.html:45 +#: templates/virtualization/virtualmachine.html:96 +#: virtualization/forms/bulk_edit.py:88 virtualization/forms/bulk_edit.py:97 +#: virtualization/forms/bulk_edit.py:106 virtualization/forms/bulk_edit.py:121 +#: virtualization/forms/bulk_import.py:58 +#: virtualization/forms/bulk_import.py:84 virtualization/forms/filtersets.py:75 +#: virtualization/forms/filtersets.py:141 +#: virtualization/forms/model_forms.py:73 +#: virtualization/forms/model_forms.py:106 +#: virtualization/forms/model_forms.py:173 virtualization/tables/clusters.py:77 +#: virtualization/tables/virtualmachines.py:51 wireless/forms/model_forms.py:77 +#: wireless/forms/model_forms.py:117 +msgid "Site" +msgstr "" + +#: circuits/filtersets.py:60 circuits/filtersets.py:213 +#: circuits/filtersets.py:250 dcim/filtersets.py:209 dcim/filtersets.py:284 +#: dcim/filtersets.py:356 extras/filtersets.py:405 ipam/filtersets.py:215 +#: ipam/filtersets.py:334 ipam/filtersets.py:921 ipam/filtersets.py:1127 +#: virtualization/filtersets.py:73 virtualization/filtersets.py:199 +msgid "Site (slug)" +msgstr "" + +#: circuits/filtersets.py:65 +msgid "ASN (ID)" +msgstr "" + +#: circuits/filtersets.py:86 circuits/filtersets.py:112 +#: circuits/filtersets.py:146 +msgid "Provider (ID)" +msgstr "" + +#: circuits/filtersets.py:92 circuits/filtersets.py:118 +#: circuits/filtersets.py:152 +msgid "Provider (slug)" +msgstr "" + +#: circuits/filtersets.py:157 +msgid "Provider account (ID)" +msgstr "" + +#: circuits/filtersets.py:162 +msgid "Provider network (ID)" +msgstr "" + +#: circuits/filtersets.py:166 +msgid "Circuit type (ID)" +msgstr "" + +#: circuits/filtersets.py:172 +msgid "Circuit type (slug)" +msgstr "" + +#: circuits/filtersets.py:207 circuits/filtersets.py:244 dcim/filtersets.py:203 +#: dcim/filtersets.py:278 dcim/filtersets.py:350 dcim/filtersets.py:897 +#: dcim/filtersets.py:1204 dcim/filtersets.py:1699 dcim/filtersets.py:1871 +#: dcim/filtersets.py:1929 ipam/filtersets.py:209 ipam/filtersets.py:328 +#: ipam/filtersets.py:915 ipam/filtersets.py:1132 +#: virtualization/filtersets.py:67 virtualization/filtersets.py:193 +msgid "Site (ID)" +msgstr "" + +#: circuits/filtersets.py:236 core/filtersets.py:72 dcim/filtersets.py:631 +#: dcim/filtersets.py:1173 dcim/filtersets.py:1975 extras/filtersets.py:40 +#: extras/filtersets.py:69 extras/filtersets.py:108 extras/filtersets.py:137 +#: extras/filtersets.py:164 extras/filtersets.py:195 extras/filtersets.py:264 +#: extras/filtersets.py:312 extras/filtersets.py:372 extras/filtersets.py:531 +#: extras/filtersets.py:573 extras/filtersets.py:614 extras/filtersets.py:637 +#: ipam/forms/model_forms.py:432 netbox/filtersets.py:275 +#: netbox/forms/__init__.py:23 netbox/forms/base.py:151 +#: templates/htmx/object_selector.html:28 templates/inc/filter_list.html:53 +#: templates/ipam/ipaddress_assign.html:32 templates/search.html:7 +#: templates/search.html:26 tenancy/filtersets.py:87 users/filtersets.py:21 +#: users/filtersets.py:37 users/filtersets.py:69 users/filtersets.py:117 +#: utilities/forms/forms.py:99 +msgid "Search" +msgstr "" + +#: circuits/filtersets.py:240 circuits/forms/bulk_edit.py:167 +#: circuits/forms/model_forms.py:110 circuits/forms/model_forms.py:132 +#: dcim/forms/connections.py:66 templates/circuits/circuit.html:15 +#: templates/dcim/inc/cable_termination.html:55 +#: templates/dcim/trace/circuit.html:4 +msgid "Circuit" +msgstr "" + +#: circuits/filtersets.py:254 +msgid "ProviderNetwork (ID)" +msgstr "" + +#: circuits/forms/bulk_edit.py:25 circuits/forms/filtersets.py:56 +#: circuits/forms/model_forms.py:26 circuits/tables/providers.py:33 +#: dcim/forms/bulk_edit.py:126 dcim/forms/filtersets.py:185 +#: dcim/forms/model_forms.py:126 dcim/tables/sites.py:94 +#: ipam/models/asns.py:126 ipam/tables/asn.py:27 ipam/views.py:221 +#: netbox/navigation/menu.py:160 netbox/navigation/menu.py:163 +#: templates/circuits/provider.html:24 +msgid "ASNs" +msgstr "" + +#: circuits/forms/bulk_edit.py:29 circuits/forms/bulk_edit.py:51 +#: circuits/forms/bulk_edit.py:78 circuits/forms/bulk_edit.py:99 +#: circuits/forms/bulk_edit.py:159 core/forms/bulk_edit.py:27 +#: dcim/forms/bulk_create.py:35 dcim/forms/bulk_edit.py:71 +#: dcim/forms/bulk_edit.py:90 dcim/forms/bulk_edit.py:149 +#: dcim/forms/bulk_edit.py:190 dcim/forms/bulk_edit.py:208 +#: dcim/forms/bulk_edit.py:336 dcim/forms/bulk_edit.py:371 +#: dcim/forms/bulk_edit.py:386 dcim/forms/bulk_edit.py:445 +#: dcim/forms/bulk_edit.py:484 dcim/forms/bulk_edit.py:514 +#: dcim/forms/bulk_edit.py:538 dcim/forms/bulk_edit.py:608 +#: dcim/forms/bulk_edit.py:657 dcim/forms/bulk_edit.py:709 +#: dcim/forms/bulk_edit.py:732 dcim/forms/bulk_edit.py:780 +#: dcim/forms/bulk_edit.py:850 dcim/forms/bulk_edit.py:903 +#: dcim/forms/bulk_edit.py:938 dcim/forms/bulk_edit.py:978 +#: dcim/forms/bulk_edit.py:1022 dcim/forms/bulk_edit.py:1067 +#: dcim/forms/bulk_edit.py:1094 dcim/forms/bulk_edit.py:1112 +#: dcim/forms/bulk_edit.py:1130 dcim/forms/bulk_edit.py:1148 +#: dcim/forms/bulk_edit.py:1566 extras/forms/bulk_edit.py:35 +#: extras/forms/bulk_edit.py:118 extras/forms/bulk_edit.py:147 +#: extras/forms/bulk_edit.py:242 extras/forms/bulk_edit.py:266 +#: extras/forms/bulk_edit.py:280 extras/tables/tables.py:78 +#: ipam/forms/bulk_edit.py:52 ipam/forms/bulk_edit.py:72 +#: ipam/forms/bulk_edit.py:92 ipam/forms/bulk_edit.py:116 +#: ipam/forms/bulk_edit.py:145 ipam/forms/bulk_edit.py:174 +#: ipam/forms/bulk_edit.py:193 ipam/forms/bulk_edit.py:262 +#: ipam/forms/bulk_edit.py:306 ipam/forms/bulk_edit.py:354 +#: ipam/forms/bulk_edit.py:397 ipam/forms/bulk_edit.py:425 +#: ipam/forms/bulk_edit.py:553 ipam/forms/bulk_edit.py:584 +#: ipam/forms/bulk_edit.py:613 templates/account/token.html:36 +#: templates/circuits/circuit.html:60 templates/circuits/circuittype.html:29 +#: templates/circuits/inc/circuit_termination.html:115 +#: templates/circuits/provider.html:34 +#: templates/circuits/providernetwork.html:35 templates/core/datasource.html:55 +#: templates/dcim/cable.html:37 templates/dcim/consoleport.html:47 +#: templates/dcim/consoleserverport.html:47 templates/dcim/device.html:113 +#: templates/dcim/devicebay.html:35 templates/dcim/devicerole.html:33 +#: templates/dcim/devicetype.html:36 templates/dcim/frontport.html:61 +#: templates/dcim/interface.html:70 templates/dcim/inventoryitem.html:61 +#: templates/dcim/inventoryitemrole.html:23 templates/dcim/location.html:36 +#: templates/dcim/manufacturer.html:43 templates/dcim/module.html:71 +#: templates/dcim/modulebay.html:39 templates/dcim/moduletype.html:27 +#: templates/dcim/platform.html:36 templates/dcim/powerfeed.html:43 +#: templates/dcim/poweroutlet.html:43 templates/dcim/powerpanel.html:31 +#: templates/dcim/powerport.html:43 templates/dcim/rack.html:61 +#: templates/dcim/rackreservation.html:69 templates/dcim/rackrole.html:29 +#: templates/dcim/rearport.html:57 templates/dcim/region.html:34 +#: templates/dcim/site.html:73 templates/dcim/sitegroup.html:34 +#: templates/dcim/virtualchassis.html:32 +#: templates/extras/admin/plugins_list.html:26 +#: templates/extras/configcontext.html:22 +#: templates/extras/configtemplate.html:18 templates/extras/customfield.html:35 +#: templates/extras/dashboard/widget_add.html:14 +#: templates/extras/exporttemplate.html:25 templates/extras/report_list.html:47 +#: templates/extras/savedfilter.html:18 templates/extras/script_list.html:53 +#: templates/extras/tag.html:23 templates/generic/bulk_import.html:118 +#: templates/ipam/aggregate.html:44 templates/ipam/asn.html:43 +#: templates/ipam/asnrange.html:39 templates/ipam/fhrpgroup.html:35 +#: templates/ipam/ipaddress.html:58 templates/ipam/iprange.html:70 +#: templates/ipam/l2vpn.html:27 templates/ipam/prefix.html:82 +#: templates/ipam/rir.html:29 templates/ipam/role.html:29 +#: templates/ipam/routetarget.html:22 templates/ipam/service.html:53 +#: templates/ipam/servicetemplate.html:28 templates/ipam/vlan.html:65 +#: templates/ipam/vlangroup.html:35 templates/ipam/vrf.html:36 +#: templates/tenancy/contact.html:68 templates/tenancy/contactgroup.html:28 +#: templates/tenancy/contactrole.html:23 templates/tenancy/tenant.html:25 +#: templates/tenancy/tenantgroup.html:36 +#: templates/users/objectpermission.html:22 templates/users/token.html:28 +#: templates/virtualization/cluster.html:28 +#: templates/virtualization/clustergroup.html:29 +#: templates/virtualization/clustertype.html:29 +#: templates/virtualization/virtualmachine.html:34 +#: templates/virtualization/vminterface.html:54 +#: templates/wireless/wirelesslan.html:27 +#: templates/wireless/wirelesslangroup.html:34 +#: templates/wireless/wirelesslink.html:37 tenancy/forms/bulk_edit.py:31 +#: tenancy/forms/bulk_edit.py:79 tenancy/forms/bulk_edit.py:121 +#: users/forms/bulk_edit.py:62 users/forms/bulk_edit.py:92 +#: virtualization/forms/bulk_edit.py:29 virtualization/forms/bulk_edit.py:43 +#: virtualization/forms/bulk_edit.py:174 virtualization/forms/bulk_edit.py:225 +#: wireless/forms/bulk_edit.py:28 wireless/forms/bulk_edit.py:81 +#: wireless/forms/bulk_edit.py:128 +msgid "Description" +msgstr "" + +#: circuits/forms/bulk_edit.py:46 circuits/forms/bulk_edit.py:68 +#: circuits/forms/bulk_edit.py:118 circuits/forms/bulk_import.py:35 +#: circuits/forms/bulk_import.py:50 circuits/forms/bulk_import.py:76 +#: circuits/forms/filtersets.py:70 circuits/forms/filtersets.py:88 +#: circuits/forms/filtersets.py:116 circuits/forms/filtersets.py:130 +#: circuits/forms/model_forms.py:32 circuits/forms/model_forms.py:44 +#: circuits/forms/model_forms.py:58 circuits/forms/model_forms.py:92 +#: circuits/tables/circuits.py:55 circuits/tables/providers.py:72 +#: circuits/tables/providers.py:103 templates/circuits/circuit.html:19 +#: templates/circuits/provider.html:20 +#: templates/circuits/provideraccount.html:21 +#: templates/circuits/providernetwork.html:23 +#: templates/dcim/inc/cable_termination.html:51 +msgid "Provider" +msgstr "" + +#: circuits/forms/bulk_edit.py:75 circuits/forms/filtersets.py:91 +#: templates/circuits/providernetwork.html:31 +msgid "Service ID" +msgstr "" + +#: circuits/forms/bulk_edit.py:95 circuits/forms/filtersets.py:107 +#: dcim/forms/bulk_edit.py:204 dcim/forms/bulk_edit.py:500 +#: dcim/forms/bulk_edit.py:694 dcim/forms/bulk_edit.py:1063 +#: dcim/forms/bulk_edit.py:1090 dcim/forms/bulk_edit.py:1562 +#: dcim/forms/filtersets.py:970 dcim/forms/filtersets.py:1344 +#: dcim/forms/filtersets.py:1365 dcim/tables/devices.py:700 +#: dcim/tables/devices.py:760 dcim/tables/devices.py:983 +#: dcim/tables/devicetypes.py:245 dcim/tables/devicetypes.py:260 +#: dcim/tables/racks.py:32 extras/forms/bulk_edit.py:238 +#: extras/tables/tables.py:323 templates/circuits/circuittype.html:33 +#: templates/dcim/cable.html:41 templates/dcim/devicerole.html:37 +#: templates/dcim/frontport.html:43 templates/dcim/inventoryitemrole.html:27 +#: templates/dcim/rackrole.html:33 templates/dcim/rearport.html:43 +#: templates/extras/tag.html:29 +msgid "Color" +msgstr "" + +#: circuits/forms/bulk_edit.py:113 circuits/forms/bulk_import.py:89 +#: circuits/forms/filtersets.py:125 core/forms/bulk_edit.py:17 +#: core/forms/filtersets.py:30 core/tables/data.py:20 core/tables/jobs.py:18 +#: dcim/forms/bulk_edit.py:281 dcim/forms/bulk_edit.py:672 +#: dcim/forms/bulk_edit.py:811 dcim/forms/bulk_edit.py:879 +#: dcim/forms/bulk_edit.py:898 dcim/forms/bulk_edit.py:921 +#: dcim/forms/bulk_edit.py:963 dcim/forms/bulk_edit.py:1007 +#: dcim/forms/bulk_edit.py:1058 dcim/forms/bulk_edit.py:1085 +#: dcim/forms/bulk_import.py:206 dcim/forms/bulk_import.py:645 +#: dcim/forms/bulk_import.py:671 dcim/forms/bulk_import.py:697 +#: dcim/forms/bulk_import.py:717 dcim/forms/bulk_import.py:800 +#: dcim/forms/bulk_import.py:890 dcim/forms/bulk_import.py:932 +#: dcim/forms/bulk_import.py:1145 dcim/forms/bulk_import.py:1304 +#: dcim/forms/filtersets.py:283 dcim/forms/filtersets.py:860 +#: dcim/forms/filtersets.py:960 dcim/forms/filtersets.py:1080 +#: dcim/forms/filtersets.py:1150 dcim/forms/filtersets.py:1172 +#: dcim/forms/filtersets.py:1194 dcim/forms/filtersets.py:1211 +#: dcim/forms/filtersets.py:1244 dcim/forms/filtersets.py:1339 +#: dcim/forms/filtersets.py:1360 dcim/forms/object_import.py:89 +#: dcim/forms/object_import.py:118 dcim/forms/object_import.py:150 +#: dcim/tables/devices.py:211 dcim/tables/devices.py:816 +#: dcim/tables/power.py:77 extras/forms/bulk_import.py:37 +#: extras/tables/tables.py:345 extras/tables/tables.py:443 +#: ipam/forms/bulk_edit.py:603 ipam/forms/bulk_import.py:524 +#: ipam/forms/filtersets.py:537 netbox/tables/tables.py:225 +#: templates/circuits/circuit.html:31 templates/core/datasource.html:39 +#: templates/dcim/cable.html:16 templates/dcim/consoleport.html:39 +#: templates/dcim/consoleserverport.html:39 templates/dcim/frontport.html:39 +#: templates/dcim/interface.html:47 templates/dcim/interface.html:171 +#: templates/dcim/interface.html:319 templates/dcim/powerfeed.html:35 +#: templates/dcim/poweroutlet.html:39 templates/dcim/powerport.html:39 +#: templates/dcim/rack.html:88 templates/dcim/rearport.html:39 +#: templates/ipam/l2vpn.html:23 templates/virtualization/cluster.html:20 +#: templates/wireless/inc/authentication_attrs.html:9 +#: templates/wireless/inc/wirelesslink_interface.html:14 +#: virtualization/forms/bulk_edit.py:57 virtualization/forms/bulk_import.py:40 +#: virtualization/forms/filtersets.py:50 virtualization/forms/model_forms.py:64 +#: virtualization/tables/clusters.py:66 +msgid "Type" +msgstr "" + +#: circuits/forms/bulk_edit.py:123 circuits/forms/bulk_import.py:82 +#: circuits/forms/filtersets.py:138 circuits/forms/model_forms.py:97 +msgid "Provider account" +msgstr "" + +#: circuits/forms/bulk_edit.py:131 circuits/forms/bulk_import.py:95 +#: circuits/forms/filtersets.py:149 core/forms/filtersets.py:35 +#: core/forms/filtersets.py:76 core/tables/data.py:23 core/tables/jobs.py:25 +#: dcim/forms/bulk_edit.py:104 dcim/forms/bulk_edit.py:179 +#: dcim/forms/bulk_edit.py:260 dcim/forms/bulk_edit.py:593 +#: dcim/forms/bulk_edit.py:646 dcim/forms/bulk_edit.py:678 +#: dcim/forms/bulk_edit.py:805 dcim/forms/bulk_edit.py:1585 +#: dcim/forms/bulk_import.py:87 dcim/forms/bulk_import.py:146 +#: dcim/forms/bulk_import.py:194 dcim/forms/bulk_import.py:442 +#: dcim/forms/bulk_import.py:596 dcim/forms/bulk_import.py:1139 +#: dcim/forms/bulk_import.py:1299 dcim/forms/filtersets.py:168 +#: dcim/forms/filtersets.py:227 dcim/forms/filtersets.py:278 +#: dcim/forms/filtersets.py:719 dcim/forms/filtersets.py:828 +#: dcim/forms/filtersets.py:864 dcim/forms/filtersets.py:965 +#: dcim/forms/filtersets.py:1075 dcim/tables/devices.py:173 +#: dcim/tables/devices.py:819 dcim/tables/devices.py:1043 +#: dcim/tables/modules.py:69 dcim/tables/power.py:74 dcim/tables/racks.py:66 +#: dcim/tables/sites.py:82 dcim/tables/sites.py:133 ipam/forms/bulk_edit.py:242 +#: ipam/forms/bulk_edit.py:291 ipam/forms/bulk_edit.py:339 +#: ipam/forms/bulk_edit.py:543 ipam/forms/bulk_import.py:194 +#: ipam/forms/bulk_import.py:259 ipam/forms/bulk_import.py:295 +#: ipam/forms/bulk_import.py:461 ipam/forms/filtersets.py:209 +#: ipam/forms/filtersets.py:274 ipam/forms/filtersets.py:344 +#: ipam/forms/filtersets.py:484 ipam/forms/model_forms.py:451 +#: ipam/tables/ip.py:236 ipam/tables/ip.py:309 ipam/tables/ip.py:359 +#: ipam/tables/ip.py:421 ipam/tables/ip.py:448 ipam/tables/vlans.py:122 +#: ipam/tables/vlans.py:227 templates/circuits/circuit.html:35 +#: templates/core/datasource.html:47 templates/core/job.html:35 +#: templates/dcim/cable.html:20 templates/dcim/device.html:200 +#: templates/dcim/location.html:48 templates/dcim/module.html:67 +#: templates/dcim/powerfeed.html:39 templates/dcim/rack.html:53 +#: templates/dcim/site.html:56 templates/extras/report_list.html:49 +#: templates/extras/script_list.html:55 templates/ipam/ipaddress.html:40 +#: templates/ipam/iprange.html:57 templates/ipam/prefix.html:74 +#: templates/ipam/vlan.html:51 templates/virtualization/cluster.html:24 +#: templates/virtualization/virtualmachine.html:22 +#: templates/wireless/wirelesslan.html:23 +#: templates/wireless/wirelesslink.html:20 users/forms/filtersets.py:35 +#: users/forms/model_forms.py:196 virtualization/forms/bulk_edit.py:67 +#: virtualization/forms/bulk_edit.py:115 virtualization/forms/bulk_import.py:53 +#: virtualization/forms/bulk_import.py:79 virtualization/forms/filtersets.py:58 +#: virtualization/forms/filtersets.py:153 virtualization/tables/clusters.py:74 +#: virtualization/tables/virtualmachines.py:48 wireless/forms/bulk_edit.py:42 +#: wireless/forms/bulk_edit.py:104 wireless/forms/bulk_import.py:43 +#: wireless/forms/bulk_import.py:84 wireless/forms/filtersets.py:48 +#: wireless/forms/filtersets.py:82 wireless/tables/wirelesslan.py:52 +#: wireless/tables/wirelesslink.py:19 +msgid "Status" +msgstr "" + +#: circuits/forms/bulk_edit.py:137 circuits/forms/bulk_import.py:100 +#: circuits/forms/filtersets.py:119 dcim/forms/bulk_edit.py:120 +#: dcim/forms/bulk_edit.py:185 dcim/forms/bulk_edit.py:255 +#: dcim/forms/bulk_edit.py:366 dcim/forms/bulk_edit.py:583 +#: dcim/forms/bulk_edit.py:684 dcim/forms/bulk_edit.py:1590 +#: dcim/forms/bulk_import.py:106 dcim/forms/bulk_import.py:151 +#: dcim/forms/bulk_import.py:187 dcim/forms/bulk_import.py:274 +#: dcim/forms/bulk_import.py:416 dcim/forms/bulk_import.py:1151 +#: dcim/forms/bulk_import.py:1356 dcim/forms/filtersets.py:164 +#: dcim/forms/filtersets.py:195 dcim/forms/filtersets.py:246 +#: dcim/forms/filtersets.py:330 dcim/forms/filtersets.py:351 +#: dcim/forms/filtersets.py:647 dcim/forms/filtersets.py:819 +#: dcim/forms/filtersets.py:884 dcim/forms/filtersets.py:914 +#: dcim/forms/filtersets.py:1035 dcim/tables/power.py:88 +#: extras/filtersets.py:486 extras/forms/filtersets.py:306 +#: extras/forms/filtersets.py:380 ipam/forms/bulk_edit.py:42 +#: ipam/forms/bulk_edit.py:67 ipam/forms/bulk_edit.py:111 +#: ipam/forms/bulk_edit.py:140 ipam/forms/bulk_edit.py:165 +#: ipam/forms/bulk_edit.py:237 ipam/forms/bulk_edit.py:286 +#: ipam/forms/bulk_edit.py:334 ipam/forms/bulk_edit.py:538 +#: ipam/forms/bulk_edit.py:608 ipam/forms/bulk_import.py:40 +#: ipam/forms/bulk_import.py:69 ipam/forms/bulk_import.py:97 +#: ipam/forms/bulk_import.py:117 ipam/forms/bulk_import.py:137 +#: ipam/forms/bulk_import.py:166 ipam/forms/bulk_import.py:252 +#: ipam/forms/bulk_import.py:288 ipam/forms/bulk_import.py:454 +#: ipam/forms/bulk_import.py:518 ipam/forms/filtersets.py:51 +#: ipam/forms/filtersets.py:71 ipam/forms/filtersets.py:103 +#: ipam/forms/filtersets.py:123 ipam/forms/filtersets.py:146 +#: ipam/forms/filtersets.py:173 ipam/forms/filtersets.py:260 +#: ipam/forms/filtersets.py:300 ipam/forms/filtersets.py:453 +#: ipam/forms/filtersets.py:534 ipam/tables/ip.py:451 ipam/tables/vlans.py:224 +#: templates/circuits/circuit.html:39 templates/dcim/cable.html:24 +#: templates/dcim/device.html:98 templates/dcim/location.html:52 +#: templates/dcim/powerfeed.html:47 templates/dcim/rack.html:44 +#: templates/dcim/rackreservation.html:56 templates/dcim/site.html:60 +#: templates/dcim/virtualdevicecontext.html:55 templates/ipam/aggregate.html:31 +#: templates/ipam/asn.html:34 templates/ipam/asnrange.html:30 +#: templates/ipam/ipaddress.html:31 templates/ipam/iprange.html:61 +#: templates/ipam/l2vpn.html:31 templates/ipam/prefix.html:29 +#: templates/ipam/routetarget.html:18 templates/ipam/vlan.html:42 +#: templates/ipam/vrf.html:23 templates/tenancy/tenant.html:17 +#: templates/virtualization/cluster.html:36 +#: templates/virtualization/virtualmachine.html:38 +#: templates/wireless/wirelesslan.html:35 +#: templates/wireless/wirelesslink.html:28 tenancy/forms/forms.py:25 +#: tenancy/forms/forms.py:48 tenancy/forms/model_forms.py:56 +#: tenancy/tables/columns.py:64 virtualization/forms/bulk_edit.py:73 +#: virtualization/forms/bulk_edit.py:152 virtualization/forms/bulk_import.py:65 +#: virtualization/forms/bulk_import.py:114 +#: virtualization/forms/filtersets.py:44 virtualization/forms/filtersets.py:98 +#: wireless/forms/bulk_edit.py:62 wireless/forms/bulk_edit.py:109 +#: wireless/forms/bulk_import.py:55 wireless/forms/bulk_import.py:97 +#: wireless/forms/filtersets.py:34 wireless/forms/filtersets.py:74 +msgid "Tenant" +msgstr "" + +#: circuits/forms/bulk_edit.py:142 circuits/forms/filtersets.py:173 +msgid "Install date" +msgstr "" + +#: circuits/forms/bulk_edit.py:147 circuits/forms/filtersets.py:178 +msgid "Termination date" +msgstr "" + +#: circuits/forms/bulk_edit.py:153 circuits/forms/filtersets.py:185 +msgid "Commit rate (Kbps)" +msgstr "" + +#: circuits/forms/bulk_edit.py:168 circuits/forms/model_forms.py:111 +msgid "Service Parameters" +msgstr "" + +#: circuits/forms/bulk_edit.py:169 circuits/forms/model_forms.py:112 +#: dcim/forms/model_forms.py:141 dcim/forms/model_forms.py:183 +#: dcim/forms/model_forms.py:260 dcim/forms/model_forms.py:671 +#: dcim/forms/model_forms.py:1477 ipam/forms/model_forms.py:63 +#: ipam/forms/model_forms.py:116 ipam/forms/model_forms.py:137 +#: ipam/forms/model_forms.py:161 ipam/forms/model_forms.py:233 +#: ipam/forms/model_forms.py:259 ipam/forms/model_forms.py:781 +#: netbox/navigation/menu.py:38 templates/dcim/cable_edit.html:68 +#: templates/dcim/device_edit.html:85 templates/dcim/rack_edit.html:30 +#: templates/ipam/ipaddress_bulk_add.html:27 +#: templates/ipam/ipaddress_edit.html:27 templates/ipam/vlan_edit.html:22 +#: virtualization/forms/model_forms.py:82 +#: virtualization/forms/model_forms.py:223 wireless/forms/model_forms.py:55 +#: wireless/forms/model_forms.py:160 +msgid "Tenancy" +msgstr "" + +#: circuits/forms/bulk_import.py:38 circuits/forms/bulk_import.py:53 +#: circuits/forms/bulk_import.py:79 +msgid "Assigned provider" +msgstr "" + +#: circuits/forms/bulk_import.py:70 dcim/forms/bulk_import.py:170 +#: dcim/forms/bulk_import.py:380 dcim/forms/bulk_import.py:1092 +#: dcim/forms/bulk_import.py:1171 extras/forms/bulk_import.py:167 +msgid "RGB color in hexadecimal. Example:" +msgstr "" + +#: circuits/forms/bulk_import.py:85 +msgid "Assigned provider account" +msgstr "" + +#: circuits/forms/bulk_import.py:92 +msgid "Type of circuit" +msgstr "" + +#: circuits/forms/bulk_import.py:97 dcim/forms/bulk_import.py:89 +#: dcim/forms/bulk_import.py:148 dcim/forms/bulk_import.py:196 +#: dcim/forms/bulk_import.py:444 dcim/forms/bulk_import.py:598 +#: dcim/forms/bulk_import.py:1301 ipam/forms/bulk_import.py:196 +#: ipam/forms/bulk_import.py:261 ipam/forms/bulk_import.py:297 +#: ipam/forms/bulk_import.py:463 virtualization/forms/bulk_import.py:55 +#: virtualization/forms/bulk_import.py:81 +msgid "Operational status" +msgstr "" + +#: circuits/forms/bulk_import.py:104 dcim/forms/bulk_import.py:110 +#: dcim/forms/bulk_import.py:155 dcim/forms/bulk_import.py:278 +#: dcim/forms/bulk_import.py:420 dcim/forms/bulk_import.py:1155 +#: dcim/forms/bulk_import.py:1296 ipam/forms/bulk_import.py:44 +#: ipam/forms/bulk_import.py:73 ipam/forms/bulk_import.py:101 +#: ipam/forms/bulk_import.py:121 ipam/forms/bulk_import.py:141 +#: ipam/forms/bulk_import.py:170 ipam/forms/bulk_import.py:256 +#: ipam/forms/bulk_import.py:292 ipam/forms/bulk_import.py:458 +#: virtualization/forms/bulk_import.py:69 +#: virtualization/forms/bulk_import.py:118 wireless/forms/bulk_import.py:59 +#: wireless/forms/bulk_import.py:101 +msgid "Assigned tenant" +msgstr "" + +#: circuits/forms/bulk_import.py:123 circuits/forms/filtersets.py:146 +#: circuits/forms/model_forms.py:143 +msgid "Provider network" +msgstr "" + +#: circuits/forms/filtersets.py:26 circuits/forms/filtersets.py:118 +#: dcim/forms/bulk_edit.py:247 dcim/forms/bulk_edit.py:345 +#: dcim/forms/bulk_edit.py:575 dcim/forms/bulk_edit.py:622 +#: dcim/forms/bulk_edit.py:772 dcim/forms/bulk_import.py:181 +#: dcim/forms/bulk_import.py:255 dcim/forms/bulk_import.py:483 +#: dcim/forms/bulk_import.py:1245 dcim/forms/bulk_import.py:1279 +#: dcim/forms/filtersets.py:91 dcim/forms/filtersets.py:243 +#: dcim/forms/filtersets.py:275 dcim/forms/filtersets.py:327 +#: dcim/forms/filtersets.py:378 dcim/forms/filtersets.py:644 +#: dcim/forms/filtersets.py:682 dcim/forms/filtersets.py:883 +#: dcim/forms/filtersets.py:912 dcim/forms/filtersets.py:932 +#: dcim/forms/filtersets.py:996 dcim/forms/filtersets.py:1025 +#: dcim/forms/filtersets.py:1034 dcim/forms/filtersets.py:1145 +#: dcim/forms/filtersets.py:1167 dcim/forms/filtersets.py:1189 +#: dcim/forms/filtersets.py:1206 dcim/forms/filtersets.py:1226 +#: dcim/forms/filtersets.py:1333 dcim/forms/filtersets.py:1355 +#: dcim/forms/filtersets.py:1376 dcim/forms/filtersets.py:1391 +#: dcim/forms/filtersets.py:1402 dcim/forms/model_forms.py:182 +#: dcim/forms/model_forms.py:216 dcim/forms/model_forms.py:402 +#: dcim/forms/model_forms.py:634 dcim/tables/devices.py:190 +#: dcim/tables/power.py:30 dcim/tables/racks.py:58 dcim/tables/racks.py:143 +#: extras/filtersets.py:410 extras/forms/filtersets.py:303 +#: ipam/forms/bulk_edit.py:458 ipam/forms/filtersets.py:172 +#: ipam/forms/filtersets.py:403 ipam/forms/filtersets.py:425 +#: ipam/forms/filtersets.py:451 ipam/forms/model_forms.py:562 +#: templates/dcim/device.html:34 templates/dcim/device_edit.html:30 +#: templates/dcim/inc/cable_termination.html:12 templates/dcim/location.html:27 +#: templates/dcim/powerpanel.html:27 templates/dcim/rack.html:27 +#: templates/dcim/rackreservation.html:34 virtualization/forms/filtersets.py:43 +#: virtualization/forms/filtersets.py:96 wireless/forms/model_forms.py:88 +#: wireless/forms/model_forms.py:128 +msgid "Location" +msgstr "" + +#: circuits/forms/filtersets.py:27 ipam/forms/model_forms.py:160 +#: ipam/models/asns.py:108 ipam/models/asns.py:125 ipam/tables/asn.py:41 +#: templates/ipam/asn.html:20 +msgid "ASN" +msgstr "" + +#: circuits/forms/filtersets.py:28 circuits/forms/filtersets.py:120 +#: dcim/forms/filtersets.py:135 dcim/forms/filtersets.py:149 +#: dcim/forms/filtersets.py:165 dcim/forms/filtersets.py:196 +#: dcim/forms/filtersets.py:247 dcim/forms/filtersets.py:331 +#: dcim/forms/filtersets.py:405 dcim/forms/filtersets.py:648 +#: dcim/forms/filtersets.py:997 netbox/navigation/menu.py:45 +#: netbox/navigation/menu.py:47 tenancy/tables/columns.py:70 +#: tenancy/tables/contacts.py:25 tenancy/views.py:23 +#: virtualization/forms/filtersets.py:34 virtualization/forms/filtersets.py:45 +#: virtualization/forms/filtersets.py:99 +msgid "Contacts" +msgstr "" + +#: circuits/forms/filtersets.py:33 circuits/forms/filtersets.py:156 +#: dcim/forms/bulk_edit.py:110 dcim/forms/bulk_edit.py:222 +#: dcim/forms/bulk_edit.py:747 dcim/forms/bulk_import.py:92 +#: dcim/forms/filtersets.py:69 dcim/forms/filtersets.py:175 +#: dcim/forms/filtersets.py:201 dcim/forms/filtersets.py:253 +#: dcim/forms/filtersets.py:356 dcim/forms/filtersets.py:659 +#: dcim/forms/filtersets.py:889 dcim/forms/filtersets.py:919 +#: dcim/forms/filtersets.py:1002 dcim/forms/filtersets.py:1041 +#: dcim/forms/filtersets.py:1451 dcim/forms/filtersets.py:1475 +#: dcim/forms/filtersets.py:1499 dcim/forms/model_forms.py:80 +#: dcim/forms/model_forms.py:115 dcim/forms/object_create.py:341 +#: dcim/tables/devices.py:176 dcim/tables/sites.py:85 extras/filtersets.py:377 +#: ipam/forms/bulk_edit.py:207 ipam/forms/bulk_edit.py:439 +#: ipam/forms/bulk_edit.py:511 ipam/forms/filtersets.py:216 +#: ipam/forms/filtersets.py:410 ipam/forms/filtersets.py:458 +#: ipam/forms/filtersets.py:576 ipam/forms/model_forms.py:534 +#: templates/dcim/device.html:17 templates/dcim/region.html:26 +#: templates/dcim/site.html:30 virtualization/forms/bulk_edit.py:78 +#: virtualization/forms/filtersets.py:55 virtualization/forms/filtersets.py:126 +#: virtualization/forms/model_forms.py:94 +msgid "Region" +msgstr "" + +#: circuits/forms/filtersets.py:38 circuits/forms/filtersets.py:161 +#: dcim/forms/bulk_edit.py:230 dcim/forms/bulk_edit.py:755 +#: dcim/forms/filtersets.py:74 dcim/forms/filtersets.py:180 +#: dcim/forms/filtersets.py:206 dcim/forms/filtersets.py:266 +#: dcim/forms/filtersets.py:361 dcim/forms/filtersets.py:664 +#: dcim/forms/filtersets.py:894 dcim/forms/filtersets.py:1007 +#: dcim/forms/filtersets.py:1046 dcim/forms/object_create.py:349 +#: extras/filtersets.py:394 ipam/forms/bulk_edit.py:212 +#: ipam/forms/bulk_edit.py:446 ipam/forms/bulk_edit.py:516 +#: ipam/forms/filtersets.py:221 ipam/forms/filtersets.py:415 +#: ipam/forms/filtersets.py:463 ipam/forms/model_forms.py:547 +#: virtualization/forms/bulk_edit.py:83 virtualization/forms/filtersets.py:65 +#: virtualization/forms/filtersets.py:131 +#: virtualization/forms/model_forms.py:100 +msgid "Site group" +msgstr "" + +#: circuits/forms/filtersets.py:51 +msgid "ASN (legacy)" +msgstr "" + +#: circuits/forms/filtersets.py:65 circuits/forms/filtersets.py:83 +#: circuits/forms/filtersets.py:102 circuits/forms/filtersets.py:117 +#: core/forms/filtersets.py:64 dcim/forms/bulk_edit.py:718 +#: dcim/forms/filtersets.py:163 dcim/forms/filtersets.py:194 +#: dcim/forms/filtersets.py:818 dcim/forms/filtersets.py:913 +#: dcim/forms/filtersets.py:1036 dcim/forms/filtersets.py:1144 +#: dcim/forms/filtersets.py:1166 dcim/forms/filtersets.py:1188 +#: dcim/forms/filtersets.py:1205 dcim/forms/filtersets.py:1222 +#: dcim/forms/filtersets.py:1332 dcim/forms/filtersets.py:1354 +#: dcim/forms/filtersets.py:1375 dcim/forms/filtersets.py:1390 +#: dcim/forms/filtersets.py:1401 extras/forms/filtersets.py:42 +#: extras/forms/filtersets.py:108 extras/forms/filtersets.py:139 +#: extras/forms/filtersets.py:179 extras/forms/filtersets.py:195 +#: extras/forms/filtersets.py:228 extras/forms/filtersets.py:425 +#: extras/forms/filtersets.py:466 ipam/forms/filtersets.py:102 +#: ipam/forms/filtersets.py:259 ipam/forms/filtersets.py:298 +#: ipam/forms/filtersets.py:371 ipam/forms/filtersets.py:452 +#: ipam/forms/filtersets.py:510 ipam/forms/filtersets.py:533 +#: virtualization/forms/filtersets.py:42 virtualization/forms/filtersets.py:97 +#: virtualization/forms/filtersets.py:187 wireless/forms/filtersets.py:33 +#: wireless/forms/filtersets.py:73 +msgid "Attributes" +msgstr "" + +#: circuits/forms/filtersets.py:73 circuits/tables/circuits.py:60 +#: circuits/tables/providers.py:66 templates/circuits/circuit.html:23 +#: templates/circuits/provideraccount.html:25 +msgid "Account" +msgstr "" + +#: circuits/forms/model_forms.py:64 +#: templates/circuits/circuittermination_edit.html:23 +#: templates/circuits/inc/circuit_termination.html:89 +#: templates/circuits/providernetwork.html:18 +msgid "Provider Network" +msgstr "" + +#: circuits/forms/model_forms.py:78 templates/circuits/circuittype.html:20 +msgid "Circuit Type" +msgstr "" + +#: circuits/models/circuits.py:25 dcim/models/cables.py:68 +#: dcim/models/device_component_templates.py:492 +#: dcim/models/device_component_templates.py:592 +#: dcim/models/device_components.py:967 dcim/models/device_components.py:1041 +#: dcim/models/device_components.py:1157 dcim/models/devices.py:467 +#: dcim/models/racks.py:43 extras/models/tags.py:31 +msgid "color" +msgstr "" + +#: circuits/models/circuits.py:34 +msgid "circuit type" +msgstr "" + +#: circuits/models/circuits.py:35 +msgid "circuit types" +msgstr "" + +#: circuits/models/circuits.py:46 +msgid "circuit ID" +msgstr "" + +#: circuits/models/circuits.py:47 +msgid "Unique circuit ID" +msgstr "" + +#: circuits/models/circuits.py:67 core/models/data.py:55 core/models/jobs.py:85 +#: dcim/models/cables.py:50 dcim/models/devices.py:641 +#: dcim/models/devices.py:1160 dcim/models/devices.py:1369 +#: dcim/models/power.py:95 dcim/models/racks.py:97 dcim/models/sites.py:154 +#: dcim/models/sites.py:266 ipam/models/ip.py:252 ipam/models/ip.py:521 +#: ipam/models/ip.py:729 ipam/models/vlans.py:173 +#: virtualization/models/clusters.py:74 +#: virtualization/models/virtualmachines.py:81 wireless/models.py:94 +#: wireless/models.py:158 +msgid "status" +msgstr "" + +#: circuits/models/circuits.py:82 +msgid "installed" +msgstr "" + +#: circuits/models/circuits.py:87 +msgid "terminates" +msgstr "" + +#: circuits/models/circuits.py:92 +msgid "commit rate (Kbps)" +msgstr "" + +#: circuits/models/circuits.py:93 +msgid "Committed rate" +msgstr "" + +#: circuits/models/circuits.py:135 +msgid "circuit" +msgstr "" + +#: circuits/models/circuits.py:136 +msgid "circuits" +msgstr "" + +#: circuits/models/circuits.py:169 +msgid "termination" +msgstr "" + +#: circuits/models/circuits.py:186 +msgid "port speed (Kbps)" +msgstr "" + +#: circuits/models/circuits.py:189 +msgid "Physical circuit speed" +msgstr "" + +#: circuits/models/circuits.py:194 +msgid "upstream speed (Kbps)" +msgstr "" + +#: circuits/models/circuits.py:195 +msgid "Upstream speed, if different from port speed" +msgstr "" + +#: circuits/models/circuits.py:200 +msgid "cross-connect ID" +msgstr "" + +#: circuits/models/circuits.py:201 +msgid "ID of the local cross-connect" +msgstr "" + +#: circuits/models/circuits.py:206 +msgid "patch panel/port(s)" +msgstr "" + +#: circuits/models/circuits.py:207 +msgid "Patch panel ID and port number(s)" +msgstr "" + +#: circuits/models/circuits.py:210 dcim/models/device_component_templates.py:62 +#: dcim/models/device_components.py:70 dcim/models/racks.py:536 +#: extras/models/configs.py:45 extras/models/configs.py:219 +#: extras/models/customfields.py:116 extras/models/models.py:343 +#: extras/models/models.py:458 extras/models/staging.py:31 +#: extras/models/tags.py:35 netbox/models/__init__.py:109 +#: netbox/models/__init__.py:144 netbox/models/__init__.py:190 +#: users/models.py:270 users/models.py:345 +#: virtualization/models/virtualmachines.py:256 +msgid "description" +msgstr "" + +#: circuits/models/circuits.py:223 +msgid "circuit termination" +msgstr "" + +#: circuits/models/circuits.py:224 +msgid "circuit terminations" +msgstr "" + +#: circuits/models/providers.py:22 circuits/models/providers.py:66 +#: circuits/models/providers.py:104 core/models/data.py:42 +#: core/models/jobs.py:46 dcim/models/device_component_templates.py:44 +#: dcim/models/device_components.py:55 dcim/models/devices.py:581 +#: dcim/models/devices.py:1300 dcim/models/devices.py:1365 +#: dcim/models/power.py:39 dcim/models/power.py:91 dcim/models/racks.py:62 +#: dcim/models/sites.py:138 extras/models/configs.py:36 +#: extras/models/configs.py:215 extras/models/customfields.py:83 +#: extras/models/models.py:55 extras/models/models.py:243 +#: extras/models/models.py:339 extras/models/models.py:448 +#: extras/models/models.py:543 extras/models/staging.py:26 +#: ipam/models/asns.py:18 ipam/models/fhrp.py:26 ipam/models/l2vpn.py:22 +#: ipam/models/services.py:52 ipam/models/services.py:88 +#: ipam/models/vlans.py:27 ipam/models/vlans.py:162 ipam/models/vrfs.py:22 +#: ipam/models/vrfs.py:79 netbox/models/__init__.py:136 +#: netbox/models/__init__.py:180 tenancy/models/contacts.py:63 +#: tenancy/models/tenants.py:20 tenancy/models/tenants.py:45 +#: users/models.py:341 virtualization/models/clusters.py:57 +#: virtualization/models/virtualmachines.py:69 +#: virtualization/models/virtualmachines.py:246 wireless/models.py:50 +msgid "name" +msgstr "" + +#: circuits/models/providers.py:25 +msgid "Full name of the provider" +msgstr "" + +#: circuits/models/providers.py:28 dcim/models/devices.py:86 +#: dcim/models/sites.py:149 extras/models/models.py:453 ipam/models/asns.py:23 +#: ipam/models/l2vpn.py:27 ipam/models/vlans.py:31 +#: netbox/models/__init__.py:140 netbox/models/__init__.py:185 +#: tenancy/models/tenants.py:25 tenancy/models/tenants.py:49 +#: wireless/models.py:55 +msgid "slug" +msgstr "" + +#: circuits/models/providers.py:42 +msgid "provider" +msgstr "" + +#: circuits/models/providers.py:43 +msgid "providers" +msgstr "" + +#: circuits/models/providers.py:63 +msgid "account ID" +msgstr "" + +#: circuits/models/providers.py:86 +msgid "provider account" +msgstr "" + +#: circuits/models/providers.py:87 +msgid "provider accounts" +msgstr "" + +#: circuits/models/providers.py:115 +msgid "service ID" +msgstr "" + +#: circuits/models/providers.py:126 +msgid "provider network" +msgstr "" + +#: circuits/models/providers.py:127 +msgid "provider networks" +msgstr "" + +#: circuits/tables/circuits.py:29 circuits/tables/providers.py:18 +#: circuits/tables/providers.py:69 circuits/tables/providers.py:99 +#: core/tables/data.py:16 core/tables/jobs.py:14 dcim/forms/filtersets.py:59 +#: dcim/forms/object_create.py:42 dcim/tables/devices.py:88 +#: dcim/tables/devices.py:125 dcim/tables/devices.py:167 +#: dcim/tables/devices.py:318 dcim/tables/devices.py:395 +#: dcim/tables/devices.py:439 dcim/tables/devices.py:485 +#: dcim/tables/devices.py:537 dcim/tables/devices.py:646 +#: dcim/tables/devices.py:727 dcim/tables/devices.py:777 +#: dcim/tables/devices.py:843 dcim/tables/devices.py:954 +#: dcim/tables/devices.py:974 dcim/tables/devices.py:1003 +#: dcim/tables/devices.py:1033 dcim/tables/devicetypes.py:32 +#: dcim/tables/power.py:22 dcim/tables/power.py:62 dcim/tables/racks.py:23 +#: dcim/tables/racks.py:53 dcim/tables/sites.py:24 dcim/tables/sites.py:51 +#: dcim/tables/sites.py:78 dcim/tables/sites.py:125 +#: extras/forms/filtersets.py:187 extras/tables/tables.py:65 +#: extras/tables/tables.py:105 extras/tables/tables.py:137 +#: extras/tables/tables.py:161 extras/tables/tables.py:226 +#: extras/tables/tables.py:273 extras/tables/tables.py:319 +#: extras/tables/tables.py:371 extras/tables/tables.py:394 +#: ipam/forms/bulk_edit.py:392 ipam/forms/filtersets.py:375 +#: ipam/tables/asn.py:16 ipam/tables/ip.py:85 ipam/tables/ip.py:159 +#: ipam/tables/l2vpn.py:23 ipam/tables/services.py:15 +#: ipam/tables/services.py:40 ipam/tables/vlans.py:64 ipam/tables/vlans.py:110 +#: ipam/tables/vrfs.py:26 ipam/tables/vrfs.py:67 +#: templates/circuits/circuittype.html:25 +#: templates/circuits/provideraccount.html:29 +#: templates/circuits/providernetwork.html:27 templates/core/datasource.html:35 +#: templates/core/job.html:31 templates/dcim/consoleport.html:31 +#: templates/dcim/consoleserverport.html:31 templates/dcim/devicebay.html:27 +#: templates/dcim/devicerole.html:29 templates/dcim/frontport.html:31 +#: templates/dcim/inc/interface_vlans_table.html:5 +#: templates/dcim/inc/panels/inventory_items.html:10 +#: templates/dcim/interface.html:39 templates/dcim/interface.html:167 +#: templates/dcim/inventoryitem.html:29 +#: templates/dcim/inventoryitemrole.html:19 templates/dcim/location.html:32 +#: templates/dcim/manufacturer.html:39 templates/dcim/modulebay.html:27 +#: templates/dcim/platform.html:32 templates/dcim/poweroutlet.html:31 +#: templates/dcim/powerport.html:31 templates/dcim/rackrole.html:25 +#: templates/dcim/rearport.html:31 templates/dcim/region.html:30 +#: templates/dcim/sitegroup.html:30 templates/dcim/virtualdevicecontext.html:21 +#: templates/extras/admin/plugins_list.html:22 +#: templates/extras/configcontext.html:14 +#: templates/extras/configtemplate.html:14 templates/extras/customfield.html:16 +#: templates/extras/customlink.html:14 templates/extras/exporttemplate.html:21 +#: templates/extras/report_list.html:46 templates/extras/savedfilter.html:14 +#: templates/extras/script_list.html:52 templates/extras/tag.html:17 +#: templates/extras/webhook.html:16 templates/ipam/asnrange.html:16 +#: templates/ipam/fhrpgroup.html:31 templates/ipam/l2vpn.html:15 +#: templates/ipam/rir.html:25 templates/ipam/role.html:25 +#: templates/ipam/routetarget.html:14 templates/ipam/service.html:27 +#: templates/ipam/servicetemplate.html:16 templates/ipam/vlan.html:38 +#: templates/ipam/vlangroup.html:31 templates/tenancy/contact.html:26 +#: templates/tenancy/contactgroup.html:24 templates/tenancy/contactrole.html:19 +#: templates/tenancy/tenantgroup.html:32 templates/users/group.html:18 +#: templates/users/objectpermission.html:18 +#: templates/virtualization/cluster.html:16 +#: templates/virtualization/clustergroup.html:25 +#: templates/virtualization/clustertype.html:25 +#: templates/virtualization/virtualmachine.html:18 +#: templates/virtualization/vminterface.html:28 +#: templates/wireless/wirelesslangroup.html:30 tenancy/tables/contacts.py:19 +#: tenancy/tables/contacts.py:41 tenancy/tables/contacts.py:56 +#: tenancy/tables/tenants.py:16 tenancy/tables/tenants.py:38 users/tables.py:62 +#: users/tables.py:79 virtualization/forms/bulk_create.py:19 +#: virtualization/forms/object_create.py:12 +#: virtualization/tables/clusters.py:17 virtualization/tables/clusters.py:39 +#: virtualization/tables/clusters.py:62 +#: virtualization/tables/virtualmachines.py:43 +#: virtualization/tables/virtualmachines.py:114 +#: wireless/tables/wirelesslan.py:18 wireless/tables/wirelesslan.py:79 +msgid "Name" +msgstr "" + +#: circuits/tables/circuits.py:38 circuits/tables/providers.py:45 +#: circuits/tables/providers.py:79 netbox/navigation/menu.py:235 +#: netbox/navigation/menu.py:239 netbox/navigation/menu.py:241 +#: templates/circuits/provider.html:61 +#: templates/circuits/provideraccount.html:46 +#: templates/circuits/providernetwork.html:54 +msgid "Circuits" +msgstr "" + +#: circuits/tables/circuits.py:52 templates/circuits/circuit.html:27 +msgid "Circuit ID" +msgstr "" + +#: circuits/tables/circuits.py:65 wireless/forms/model_forms.py:157 +msgid "Side A" +msgstr "" + +#: circuits/tables/circuits.py:69 +msgid "Side Z" +msgstr "" + +#: circuits/tables/circuits.py:72 templates/circuits/circuit.html:56 +msgid "Commit Rate" +msgstr "" + +#: circuits/tables/circuits.py:75 circuits/tables/providers.py:48 +#: circuits/tables/providers.py:82 circuits/tables/providers.py:107 +#: dcim/tables/devices.py:1016 dcim/tables/devicetypes.py:92 +#: dcim/tables/modules.py:29 dcim/tables/modules.py:72 dcim/tables/power.py:39 +#: dcim/tables/power.py:91 dcim/tables/racks.py:76 dcim/tables/racks.py:156 +#: dcim/tables/sites.py:103 extras/forms/bulk_edit.py:299 +#: extras/tables/tables.py:485 ipam/tables/asn.py:68 ipam/tables/fhrp.py:34 +#: ipam/tables/ip.py:135 ipam/tables/ip.py:272 ipam/tables/ip.py:325 +#: ipam/tables/ip.py:392 ipam/tables/l2vpn.py:37 ipam/tables/services.py:24 +#: ipam/tables/services.py:54 ipam/tables/vlans.py:141 ipam/tables/vrfs.py:46 +#: ipam/tables/vrfs.py:71 templates/dcim/cable_edit.html:85 +#: templates/generic/bulk_edit.html:102 templates/inc/panels/comments.html:6 +#: tenancy/tables/contacts.py:68 tenancy/tables/tenants.py:46 +#: utilities/forms/fields/fields.py:29 virtualization/tables/clusters.py:91 +#: virtualization/tables/virtualmachines.py:66 +#: wireless/tables/wirelesslan.py:27 wireless/tables/wirelesslan.py:58 +msgid "Comments" +msgstr "" + +#: circuits/tables/providers.py:23 +msgid "Accounts" +msgstr "" + +#: circuits/tables/providers.py:29 +msgid "Account Count" +msgstr "" + +#: circuits/tables/providers.py:39 dcim/tables/sites.py:100 +msgid "ASN Count" +msgstr "" + +#: core/choices.py:18 +msgid "New" +msgstr "" + +#: core/choices.py:19 +msgid "Queued" +msgstr "" + +#: core/choices.py:20 +msgid "Syncing" +msgstr "" + +#: core/choices.py:21 core/choices.py:57 core/tables/jobs.py:40 +#: extras/choices.py:199 templates/core/job.html:69 +msgid "Completed" +msgstr "" + +#: core/choices.py:22 core/choices.py:59 dcim/choices.py:176 +#: dcim/choices.py:222 dcim/choices.py:1496 extras/choices.py:201 +#: virtualization/choices.py:47 +msgid "Failed" +msgstr "" + +#: core/choices.py:35 netbox/navigation/menu.py:311 +#: templates/extras/script/base.html:14 templates/extras/script_list.html:6 +#: templates/extras/script_list.html:20 templates/extras/script_result.html:18 +msgid "Scripts" +msgstr "" + +#: core/choices.py:36 netbox/navigation/menu.py:305 +#: templates/extras/report/base.html:13 templates/extras/report_list.html:7 +#: templates/extras/report_list.html:12 +msgid "Reports" +msgstr "" + +#: core/choices.py:54 extras/choices.py:196 +msgid "Pending" +msgstr "" + +#: core/choices.py:55 core/tables/jobs.py:31 extras/choices.py:197 +#: templates/core/job.html:56 +msgid "Scheduled" +msgstr "" + +#: core/choices.py:56 extras/choices.py:198 +msgid "Running" +msgstr "" + +#: core/choices.py:58 extras/choices.py:200 +msgid "Errored" +msgstr "" + +#: core/data_backends.py:29 templates/dcim/interface.html:220 +msgid "Local" +msgstr "" + +#: core/data_backends.py:47 extras/tables/tables.py:431 +#: templates/account/profile.html:16 templates/users/user.html:18 +#: users/tables.py:31 +msgid "Username" +msgstr "" + +#: core/data_backends.py:49 core/data_backends.py:55 +msgid "Only used for cloning with HTTP(S)" +msgstr "" + +#: core/data_backends.py:53 templates/account/base.html:17 +#: templates/account/password.html:11 users/forms/model_forms.py:171 +msgid "Password" +msgstr "" + +#: core/data_backends.py:59 +msgid "Branch" +msgstr "" + +#: core/data_backends.py:118 +msgid "AWS access key ID" +msgstr "" + +#: core/data_backends.py:122 +msgid "AWS secret access key" +msgstr "" + +#: core/filtersets.py:48 extras/filtersets.py:172 extras/filtersets.py:507 +#: extras/filtersets.py:535 +msgid "Data source (ID)" +msgstr "" + +#: core/filtersets.py:54 +msgid "Data source (name)" +msgstr "" + +#: core/forms/bulk_edit.py:24 ipam/forms/bulk_edit.py:49 +msgid "Enforce unique space" +msgstr "" + +#: core/forms/bulk_edit.py:33 extras/forms/model_forms.py:196 +#: templates/extras/savedfilter.html:57 +msgid "Parameters" +msgstr "" + +#: core/forms/bulk_edit.py:37 templates/core/datasource.html:69 +msgid "Ignore rules" +msgstr "" + +#: core/forms/filtersets.py:27 core/forms/model_forms.py:89 +#: extras/forms/model_forms.py:159 extras/forms/model_forms.py:352 +#: extras/forms/model_forms.py:405 extras/tables/tables.py:171 +#: extras/tables/tables.py:363 extras/tables/tables.py:398 +#: templates/core/datasource.html:31 +#: templates/dcim/device/render_config.html:19 +#: templates/extras/configcontext.html:30 +#: templates/extras/configtemplate.html:22 +#: templates/extras/exporttemplate.html:41 +#: templates/virtualization/virtualmachine/render_config.html:19 +msgid "Data Source" +msgstr "" + +#: core/forms/filtersets.py:40 core/tables/data.py:26 +#: dcim/forms/bulk_edit.py:1012 dcim/forms/bulk_edit.py:1285 +#: dcim/forms/filtersets.py:1261 dcim/tables/devices.py:562 +#: dcim/tables/devicetypes.py:221 extras/forms/bulk_edit.py:92 +#: extras/forms/bulk_edit.py:156 extras/forms/bulk_edit.py:177 +#: extras/forms/filtersets.py:116 extras/forms/filtersets.py:203 +#: extras/forms/filtersets.py:242 extras/tables/tables.py:144 +#: extras/tables/tables.py:233 extras/tables/tables.py:280 +#: templates/core/datasource.html:43 templates/dcim/interface.html:62 +#: templates/extras/customlink.html:18 templates/extras/savedfilter.html:26 +#: templates/extras/webhook.html:20 templates/users/objectpermission.html:26 +#: templates/virtualization/vminterface.html:32 users/forms/bulk_edit.py:69 +#: users/forms/filtersets.py:73 users/tables.py:86 +#: virtualization/forms/bulk_edit.py:214 virtualization/forms/filtersets.py:203 +msgid "Enabled" +msgstr "" + +#: core/forms/filtersets.py:52 core/forms/mixins.py:21 +msgid "File" +msgstr "" + +#: core/forms/filtersets.py:57 core/forms/mixins.py:16 +#: extras/forms/filtersets.py:144 extras/forms/filtersets.py:311 +#: extras/forms/filtersets.py:397 +msgid "Data source" +msgstr "" + +#: core/forms/filtersets.py:65 extras/forms/filtersets.py:424 +msgid "Creation" +msgstr "" + +#: core/forms/filtersets.py:71 extras/forms/filtersets.py:448 +#: extras/forms/filtersets.py:494 extras/tables/tables.py:474 +#: ipam/tables/l2vpn.py:59 templates/core/job.html:25 +#: templates/extras/objectchange.html:56 tenancy/tables/contacts.py:90 +msgid "Object Type" +msgstr "" + +#: core/forms/filtersets.py:81 +msgid "Created after" +msgstr "" + +#: core/forms/filtersets.py:86 +msgid "Created before" +msgstr "" + +#: core/forms/filtersets.py:91 +msgid "Scheduled after" +msgstr "" + +#: core/forms/filtersets.py:96 +msgid "Scheduled before" +msgstr "" + +#: core/forms/filtersets.py:101 +msgid "Started after" +msgstr "" + +#: core/forms/filtersets.py:106 +msgid "Started before" +msgstr "" + +#: core/forms/filtersets.py:111 +msgid "Completed after" +msgstr "" + +#: core/forms/filtersets.py:116 +msgid "Completed before" +msgstr "" + +#: core/forms/filtersets.py:123 dcim/forms/bulk_edit.py:359 +#: dcim/forms/filtersets.py:349 dcim/forms/filtersets.py:393 +#: dcim/forms/model_forms.py:251 extras/forms/filtersets.py:440 +#: extras/forms/filtersets.py:486 templates/dcim/rackreservation.html:65 +#: templates/extras/objectchange.html:40 templates/extras/savedfilter.html:22 +#: templates/users/token.html:22 templates/users/user.html:6 +#: templates/users/user.html:14 users/filtersets.py:74 users/filtersets.py:134 +#: users/forms/filtersets.py:87 users/forms/filtersets.py:128 +#: users/forms/model_forms.py:156 users/forms/model_forms.py:194 +#: users/tables.py:19 +msgid "User" +msgstr "" + +#: core/forms/model_forms.py:46 core/tables/data.py:46 +#: templates/core/datafile.html:36 templates/extras/report/base.html:33 +#: templates/extras/script/base.html:32 templates/extras/script_result.html:45 +msgid "Source" +msgstr "" + +#: core/forms/model_forms.py:50 +msgid "Backend Parameters" +msgstr "" + +#: core/forms/model_forms.py:88 +msgid "File Upload" +msgstr "" + +#: core/models/data.py:47 dcim/models/cables.py:44 +#: dcim/models/device_component_templates.py:178 +#: dcim/models/device_component_templates.py:212 +#: dcim/models/device_component_templates.py:247 +#: dcim/models/device_component_templates.py:309 +#: dcim/models/device_component_templates.py:388 +#: dcim/models/device_component_templates.py:487 +#: dcim/models/device_component_templates.py:587 +#: dcim/models/device_components.py:285 dcim/models/device_components.py:314 +#: dcim/models/device_components.py:347 dcim/models/device_components.py:465 +#: dcim/models/device_components.py:603 dcim/models/device_components.py:962 +#: dcim/models/device_components.py:1036 dcim/models/power.py:101 +#: dcim/models/racks.py:127 extras/models/customfields.py:69 +#: extras/models/search.py:41 ipam/models/l2vpn.py:32 +#: virtualization/models/clusters.py:61 +msgid "type" +msgstr "" + +#: core/models/data.py:52 extras/choices.py:34 extras/models/models.py:86 +#: templates/core/datasource.html:59 +msgid "URL" +msgstr "" + +#: core/models/data.py:62 dcim/models/device_component_templates.py:393 +#: dcim/models/device_components.py:514 extras/models/models.py:93 +#: extras/models/models.py:248 extras/models/models.py:473 users/models.py:350 +msgid "enabled" +msgstr "" + +#: core/models/data.py:66 +msgid "ignore rules" +msgstr "" + +#: core/models/data.py:68 +msgid "Patterns (one per line) matching files to ignore when syncing" +msgstr "" + +#: core/models/data.py:71 extras/models/models.py:481 +msgid "parameters" +msgstr "" + +#: core/models/data.py:76 +msgid "last synced" +msgstr "" + +#: core/models/data.py:84 +msgid "data source" +msgstr "" + +#: core/models/data.py:85 +msgid "data sources" +msgstr "" + +#: core/models/data.py:124 +#, python-brace-format +msgid "Unknown backend type: {type}" +msgstr "" + +#: core/models/data.py:259 core/models/files.py:26 core/models/jobs.py:50 +#: extras/models/models.py:663 extras/models/models.py:704 +#: netbox/models/features.py:51 users/models.py:245 +msgid "created" +msgstr "" + +#: core/models/data.py:263 core/models/files.py:30 netbox/models/features.py:57 +msgid "last updated" +msgstr "" + +#: core/models/data.py:273 dcim/models/cables.py:417 +msgid "path" +msgstr "" + +#: core/models/data.py:276 +msgid "File path relative to the data source's root" +msgstr "" + +#: core/models/data.py:280 ipam/models/ip.py:502 +msgid "size" +msgstr "" + +#: core/models/data.py:283 +msgid "hash" +msgstr "" + +#: core/models/data.py:287 +msgid "Length must be 64 hexadecimal characters." +msgstr "" + +#: core/models/data.py:289 +msgid "SHA256 hash of the file data" +msgstr "" + +#: core/models/data.py:306 +msgid "data file" +msgstr "" + +#: core/models/data.py:307 +msgid "data files" +msgstr "" + +#: core/models/data.py:391 +msgid "auto sync record" +msgstr "" + +#: core/models/data.py:392 +msgid "auto sync records" +msgstr "" + +#: core/models/files.py:36 +msgid "file root" +msgstr "" + +#: core/models/files.py:41 +msgid "file path" +msgstr "" + +#: core/models/files.py:43 +msgid "File path relative to the designated root path" +msgstr "" + +#: core/models/files.py:59 +msgid "managed file" +msgstr "" + +#: core/models/files.py:60 +msgid "managed files" +msgstr "" + +#: core/models/jobs.py:54 +msgid "scheduled" +msgstr "" + +#: core/models/jobs.py:59 +msgid "interval" +msgstr "" + +#: core/models/jobs.py:65 +msgid "Recurrence interval (in minutes)" +msgstr "" + +#: core/models/jobs.py:68 +msgid "started" +msgstr "" + +#: core/models/jobs.py:73 +msgid "completed" +msgstr "" + +#: core/models/jobs.py:91 extras/models/staging.py:87 +msgid "data" +msgstr "" + +#: core/models/jobs.py:96 +msgid "job ID" +msgstr "" + +#: core/models/jobs.py:104 +msgid "job" +msgstr "" + +#: core/models/jobs.py:105 +msgid "jobs" +msgstr "" + +#: core/tables/data.py:50 templates/core/datafile.html:40 +msgid "Path" +msgstr "" + +#: core/tables/data.py:54 templates/extras/inc/result_pending.html:7 +msgid "Last updated" +msgstr "" + +#: core/tables/jobs.py:10 dcim/tables/devicetypes.py:161 +#: extras/tables/tables.py:196 extras/tables/tables.py:340 +#: netbox/tables/tables.py:180 templates/dcim/virtualchassis_edit.html:53 +#: wireless/tables/wirelesslink.py:16 +msgid "ID" +msgstr "" + +#: core/tables/jobs.py:21 extras/choices.py:38 extras/tables/tables.py:258 +#: extras/tables/tables.py:350 extras/tables/tables.py:448 +#: extras/tables/tables.py:479 ipam/tables/l2vpn.py:64 +#: netbox/tables/tables.py:229 templates/extras/htmx/report_result.html:45 +#: templates/extras/journalentry.html:21 templates/extras/objectchange.html:62 +#: tenancy/tables/contacts.py:93 +msgid "Object" +msgstr "" + +#: core/tables/jobs.py:34 +msgid "Interval" +msgstr "" + +#: core/tables/jobs.py:37 templates/core/job.html:65 +#: templates/extras/htmx/report_result.html:7 +#: templates/extras/htmx/script_result.html:8 +msgid "Started" +msgstr "" + +#: dcim/api/serializers.py:205 templates/dcim/rack.html:40 +msgid "Facility ID" +msgstr "" + +#: dcim/api/serializers.py:321 dcim/api/serializers.py:680 +msgid "Position (U)" +msgstr "" + +#: dcim/choices.py:21 virtualization/choices.py:21 +msgid "Staging" +msgstr "" + +#: dcim/choices.py:23 dcim/choices.py:178 dcim/choices.py:223 +#: dcim/choices.py:1420 virtualization/choices.py:23 +#: virtualization/choices.py:48 +msgid "Decommissioning" +msgstr "" + +#: dcim/choices.py:24 +msgid "Retired" +msgstr "" + +#: dcim/choices.py:65 +msgid "2-post frame" +msgstr "" + +#: dcim/choices.py:66 +msgid "4-post frame" +msgstr "" + +#: dcim/choices.py:67 +msgid "4-post cabinet" +msgstr "" + +#: dcim/choices.py:68 +msgid "Wall-mounted frame" +msgstr "" + +#: dcim/choices.py:69 +msgid "Wall-mounted frame (vertical)" +msgstr "" + +#: dcim/choices.py:70 +msgid "Wall-mounted cabinet" +msgstr "" + +#: dcim/choices.py:71 +msgid "Wall-mounted cabinet (vertical)" +msgstr "" + +#: dcim/choices.py:83 dcim/choices.py:84 dcim/choices.py:85 dcim/choices.py:86 +#, python-brace-format +msgid "{n} inches" +msgstr "" + +#: dcim/choices.py:100 ipam/choices.py:32 ipam/choices.py:50 ipam/choices.py:70 +#: ipam/choices.py:155 wireless/choices.py:26 +msgid "Reserved" +msgstr "" + +#: dcim/choices.py:101 templates/dcim/device.html:279 +msgid "Available" +msgstr "" + +#: dcim/choices.py:104 ipam/choices.py:33 ipam/choices.py:51 ipam/choices.py:71 +#: ipam/choices.py:156 wireless/choices.py:28 +msgid "Deprecated" +msgstr "" + +#: dcim/choices.py:114 templates/dcim/rack.html:135 +msgid "Millimeters" +msgstr "" + +#: dcim/choices.py:115 dcim/choices.py:1442 +msgid "Inches" +msgstr "" + +#: dcim/choices.py:140 dcim/forms/bulk_edit.py:66 dcim/forms/bulk_edit.py:85 +#: dcim/forms/bulk_edit.py:171 dcim/forms/bulk_edit.py:1290 +#: dcim/forms/bulk_import.py:59 dcim/forms/bulk_import.py:73 +#: dcim/forms/bulk_import.py:136 dcim/forms/bulk_import.py:503 +#: dcim/forms/bulk_import.py:770 dcim/forms/bulk_import.py:1021 +#: dcim/forms/filtersets.py:224 dcim/forms/model_forms.py:73 +#: dcim/forms/model_forms.py:94 dcim/forms/model_forms.py:172 +#: dcim/forms/model_forms.py:954 dcim/forms/model_forms.py:1295 +#: dcim/forms/object_import.py:181 dcim/tables/devices.py:654 +#: extras/tables/tables.py:203 ipam/tables/fhrp.py:59 ipam/tables/ip.py:374 +#: ipam/tables/services.py:44 templates/dcim/interface.html:97 +#: templates/dcim/interface.html:317 templates/dcim/location.html:44 +#: templates/dcim/region.html:38 templates/dcim/sitegroup.html:38 +#: templates/ipam/service.html:31 templates/tenancy/contactgroup.html:32 +#: templates/tenancy/tenantgroup.html:40 +#: templates/virtualization/vminterface.html:42 +#: templates/wireless/wirelesslangroup.html:38 tenancy/forms/bulk_edit.py:26 +#: tenancy/forms/bulk_edit.py:60 tenancy/forms/bulk_import.py:24 +#: tenancy/forms/bulk_import.py:58 tenancy/forms/model_forms.py:27 +#: tenancy/forms/model_forms.py:72 virtualization/forms/bulk_edit.py:204 +#: virtualization/forms/bulk_import.py:150 +#: virtualization/tables/virtualmachines.py:136 wireless/forms/bulk_edit.py:23 +#: wireless/forms/bulk_import.py:21 wireless/forms/model_forms.py:20 +msgid "Parent" +msgstr "" + +#: dcim/choices.py:141 +msgid "Child" +msgstr "" + +#: dcim/choices.py:155 templates/dcim/device.html:362 +#: templates/dcim/rack.html:188 templates/dcim/rack_elevation_list.html:22 +#: templates/dcim/rackreservation.html:84 +msgid "Front" +msgstr "" + +#: dcim/choices.py:156 templates/dcim/device.html:368 +#: templates/dcim/rack.html:194 templates/dcim/rack_elevation_list.html:23 +#: templates/dcim/rackreservation.html:90 +msgid "Rear" +msgstr "" + +#: dcim/choices.py:175 dcim/choices.py:221 virtualization/choices.py:46 +msgid "Staged" +msgstr "" + +#: dcim/choices.py:177 +msgid "Inventory" +msgstr "" + +#: dcim/choices.py:193 +msgid "Front to rear" +msgstr "" + +#: dcim/choices.py:194 +msgid "Rear to front" +msgstr "" + +#: dcim/choices.py:195 +msgid "Left to right" +msgstr "" + +#: dcim/choices.py:196 +msgid "Right to left" +msgstr "" + +#: dcim/choices.py:197 +msgid "Side to rear" +msgstr "" + +#: dcim/choices.py:198 dcim/choices.py:1215 +msgid "Passive" +msgstr "" + +#: dcim/choices.py:199 +msgid "Mixed" +msgstr "" + +#: dcim/choices.py:443 dcim/choices.py:680 +msgid "NEMA (Non-locking)" +msgstr "" + +#: dcim/choices.py:465 dcim/choices.py:702 +msgid "NEMA (Locking)" +msgstr "" + +#: dcim/choices.py:488 dcim/choices.py:725 +msgid "California Style" +msgstr "" + +#: dcim/choices.py:496 +msgid "International/ITA" +msgstr "" + +#: dcim/choices.py:526 dcim/choices.py:755 +msgid "Proprietary" +msgstr "" + +#: dcim/choices.py:534 dcim/choices.py:764 dcim/choices.py:1131 +#: dcim/choices.py:1133 dcim/choices.py:1338 dcim/choices.py:1340 +#: netbox/navigation/menu.py:188 +msgid "Other" +msgstr "" + +#: dcim/choices.py:733 +msgid "ITA/International" +msgstr "" + +#: dcim/choices.py:794 +msgid "Physical" +msgstr "" + +#: dcim/choices.py:795 dcim/choices.py:949 +msgid "Virtual" +msgstr "" + +#: dcim/choices.py:796 dcim/choices.py:1019 dcim/forms/bulk_edit.py:1398 +#: dcim/forms/filtersets.py:1225 dcim/forms/model_forms.py:880 +#: dcim/forms/model_forms.py:1189 netbox/navigation/menu.py:128 +#: netbox/navigation/menu.py:132 templates/dcim/interface.html:213 +msgid "Wireless" +msgstr "" + +#: dcim/choices.py:947 +msgid "Virtual interfaces" +msgstr "" + +#: dcim/choices.py:950 dcim/forms/bulk_edit.py:1295 +#: dcim/forms/bulk_import.py:777 dcim/forms/model_forms.py:868 +#: dcim/tables/devices.py:658 templates/dcim/interface.html:101 +#: templates/virtualization/vminterface.html:46 +#: virtualization/forms/bulk_edit.py:209 +#: virtualization/forms/bulk_import.py:157 +#: virtualization/tables/virtualmachines.py:140 +msgid "Bridge" +msgstr "" + +#: dcim/choices.py:951 +msgid "Link Aggregation Group (LAG)" +msgstr "" + +#: dcim/choices.py:955 +msgid "Ethernet (fixed)" +msgstr "" + +#: dcim/choices.py:969 +msgid "Ethernet (modular)" +msgstr "" + +#: dcim/choices.py:1005 +msgid "Ethernet (backplane)" +msgstr "" + +#: dcim/choices.py:1033 +msgid "Cellular" +msgstr "" + +#: dcim/choices.py:1080 dcim/forms/filtersets.py:299 +#: dcim/forms/filtersets.py:729 dcim/forms/filtersets.py:869 +#: dcim/forms/filtersets.py:1417 templates/dcim/inventoryitem.html:53 +#: templates/dcim/virtualchassis_edit.html:55 +msgid "Serial" +msgstr "" + +#: dcim/choices.py:1095 +msgid "Coaxial" +msgstr "" + +#: dcim/choices.py:1112 +msgid "Stacking" +msgstr "" + +#: dcim/choices.py:1162 +msgid "Half" +msgstr "" + +#: dcim/choices.py:1163 +msgid "Full" +msgstr "" + +#: dcim/choices.py:1164 wireless/choices.py:480 +msgid "Auto" +msgstr "" + +#: dcim/choices.py:1175 +msgid "Access" +msgstr "" + +#: dcim/choices.py:1176 ipam/tables/vlans.py:168 ipam/tables/vlans.py:213 +#: templates/dcim/inc/interface_vlans_table.html:7 +msgid "Tagged" +msgstr "" + +#: dcim/choices.py:1177 +msgid "Tagged (All)" +msgstr "" + +#: dcim/choices.py:1206 +msgid "IEEE Standard" +msgstr "" + +#: dcim/choices.py:1217 +msgid "Passive 24V (2-pair)" +msgstr "" + +#: dcim/choices.py:1218 +msgid "Passive 24V (4-pair)" +msgstr "" + +#: dcim/choices.py:1219 +msgid "Passive 48V (2-pair)" +msgstr "" + +#: dcim/choices.py:1220 +msgid "Passive 48V (4-pair)" +msgstr "" + +#: dcim/choices.py:1282 dcim/choices.py:1378 +msgid "Copper" +msgstr "" + +#: dcim/choices.py:1305 +msgid "Fiber Optic" +msgstr "" + +#: dcim/choices.py:1394 +msgid "Fiber" +msgstr "" + +#: dcim/choices.py:1407 dcim/forms/bulk_edit.py:859 +#: dcim/forms/bulk_edit.py:1242 dcim/forms/bulk_edit.py:1260 +#: dcim/tables/racks.py:89 extras/forms/model_forms.py:489 +#: netbox/navigation/menu.py:257 netbox/navigation/menu.py:261 +msgid "Power" +msgstr "" + +#: dcim/choices.py:1418 dcim/forms/filtersets.py:1132 +msgid "Connected" +msgstr "" + +#: dcim/choices.py:1437 +msgid "Kilometers" +msgstr "" + +#: dcim/choices.py:1438 templates/dcim/cable_trace.html:62 +msgid "Meters" +msgstr "" + +#: dcim/choices.py:1439 +msgid "Centimeters" +msgstr "" + +#: dcim/choices.py:1440 +msgid "Miles" +msgstr "" + +#: dcim/choices.py:1441 templates/dcim/cable_trace.html:63 +msgid "Feet" +msgstr "" + +#: dcim/choices.py:1457 templates/dcim/device.html:349 +#: templates/dcim/rack.html:164 +msgid "Kilograms" +msgstr "" + +#: dcim/choices.py:1458 +msgid "Grams" +msgstr "" + +#: dcim/choices.py:1459 templates/dcim/rack.html:165 +msgid "Pounds" +msgstr "" + +#: dcim/choices.py:1460 +msgid "Ounces" +msgstr "" + +#: dcim/choices.py:1506 tenancy/choices.py:17 +msgid "Primary" +msgstr "" + +#: dcim/choices.py:1507 +msgid "Redundant" +msgstr "" + +#: dcim/choices.py:1528 +msgid "Single phase" +msgstr "" + +#: dcim/choices.py:1529 +msgid "Three-phase" +msgstr "" + +#: dcim/filtersets.py:78 +msgid "Parent region (ID)" +msgstr "" + +#: dcim/filtersets.py:84 +msgid "Parent region (slug)" +msgstr "" + +#: dcim/filtersets.py:95 +msgid "Parent site group (ID)" +msgstr "" + +#: dcim/filtersets.py:101 +msgid "Parent site group (slug)" +msgstr "" + +#: dcim/filtersets.py:130 ipam/filtersets.py:792 ipam/filtersets.py:925 +msgid "Group (ID)" +msgstr "" + +#: dcim/filtersets.py:136 +msgid "Group (slug)" +msgstr "" + +#: dcim/filtersets.py:142 dcim/filtersets.py:147 +msgid "AS (ID)" +msgstr "" + +#: dcim/filtersets.py:215 dcim/filtersets.py:290 dcim/filtersets.py:388 +#: dcim/filtersets.py:909 dcim/filtersets.py:1215 dcim/filtersets.py:1883 +msgid "Location (ID)" +msgstr "" + +#: dcim/filtersets.py:222 dcim/filtersets.py:297 dcim/filtersets.py:395 +#: dcim/filtersets.py:1221 extras/filtersets.py:416 +msgid "Location (slug)" +msgstr "" + +#: dcim/filtersets.py:311 dcim/filtersets.py:762 dcim/filtersets.py:846 +#: dcim/filtersets.py:1621 ipam/filtersets.py:346 ipam/filtersets.py:458 +#: ipam/filtersets.py:935 virtualization/filtersets.py:206 +msgid "Role (ID)" +msgstr "" + +#: dcim/filtersets.py:317 dcim/filtersets.py:768 dcim/filtersets.py:852 +#: dcim/filtersets.py:1627 extras/filtersets.py:432 ipam/filtersets.py:352 +#: ipam/filtersets.py:464 ipam/filtersets.py:941 +#: virtualization/filtersets.py:212 +msgid "Role (slug)" +msgstr "" + +#: dcim/filtersets.py:345 dcim/filtersets.py:914 dcim/filtersets.py:1226 +#: dcim/filtersets.py:1944 +msgid "Rack (ID)" +msgstr "" + +#: dcim/filtersets.py:399 extras/filtersets.py:203 extras/filtersets.py:247 +#: extras/filtersets.py:287 extras/filtersets.py:582 +msgid "User (ID)" +msgstr "" + +#: dcim/filtersets.py:405 extras/filtersets.py:209 extras/filtersets.py:253 +#: extras/filtersets.py:293 users/filtersets.py:80 users/filtersets.py:140 +msgid "User (name)" +msgstr "" + +#: dcim/filtersets.py:433 dcim/filtersets.py:559 dcim/filtersets.py:752 +#: dcim/filtersets.py:803 dcim/filtersets.py:825 dcim/filtersets.py:1118 +#: dcim/filtersets.py:1611 +msgid "Manufacturer (ID)" +msgstr "" + +#: dcim/filtersets.py:439 dcim/filtersets.py:565 dcim/filtersets.py:758 +#: dcim/filtersets.py:809 dcim/filtersets.py:831 dcim/filtersets.py:1124 +#: dcim/filtersets.py:1617 +msgid "Manufacturer (slug)" +msgstr "" + +#: dcim/filtersets.py:443 +msgid "Default platform (ID)" +msgstr "" + +#: dcim/filtersets.py:449 +msgid "Default platform (slug)" +msgstr "" + +#: dcim/filtersets.py:452 dcim/forms/filtersets.py:448 +msgid "Has a front image" +msgstr "" + +#: dcim/filtersets.py:456 dcim/forms/filtersets.py:455 +msgid "Has a rear image" +msgstr "" + +#: dcim/filtersets.py:461 dcim/filtersets.py:569 dcim/filtersets.py:967 +#: dcim/forms/filtersets.py:462 dcim/forms/filtersets.py:558 +#: dcim/forms/filtersets.py:768 +msgid "Has console ports" +msgstr "" + +#: dcim/filtersets.py:465 dcim/filtersets.py:573 dcim/filtersets.py:971 +#: dcim/forms/filtersets.py:469 dcim/forms/filtersets.py:565 +#: dcim/forms/filtersets.py:775 +msgid "Has console server ports" +msgstr "" + +#: dcim/filtersets.py:469 dcim/filtersets.py:577 dcim/filtersets.py:975 +#: dcim/forms/filtersets.py:476 dcim/forms/filtersets.py:572 +#: dcim/forms/filtersets.py:782 +msgid "Has power ports" +msgstr "" + +#: dcim/filtersets.py:473 dcim/filtersets.py:581 dcim/filtersets.py:979 +#: dcim/forms/filtersets.py:483 dcim/forms/filtersets.py:579 +#: dcim/forms/filtersets.py:789 +msgid "Has power outlets" +msgstr "" + +#: dcim/filtersets.py:477 dcim/filtersets.py:585 dcim/filtersets.py:983 +#: dcim/forms/filtersets.py:490 dcim/forms/filtersets.py:586 +#: dcim/forms/filtersets.py:796 +msgid "Has interfaces" +msgstr "" + +#: dcim/filtersets.py:481 dcim/filtersets.py:589 dcim/filtersets.py:987 +#: dcim/forms/filtersets.py:497 dcim/forms/filtersets.py:593 +#: dcim/forms/filtersets.py:803 +msgid "Has pass-through ports" +msgstr "" + +#: dcim/filtersets.py:485 dcim/filtersets.py:991 dcim/forms/filtersets.py:511 +msgid "Has module bays" +msgstr "" + +#: dcim/filtersets.py:489 dcim/filtersets.py:995 dcim/forms/filtersets.py:504 +msgid "Has device bays" +msgstr "" + +#: dcim/filtersets.py:493 dcim/forms/filtersets.py:518 +msgid "Has inventory items" +msgstr "" + +#: dcim/filtersets.py:636 dcim/filtersets.py:841 dcim/filtersets.py:1247 +msgid "Device type (ID)" +msgstr "" + +#: dcim/filtersets.py:649 dcim/filtersets.py:1129 +msgid "Module type (ID)" +msgstr "" + +#: dcim/filtersets.py:748 dcim/filtersets.py:1607 +msgid "Parent inventory item (ID)" +msgstr "" + +#: dcim/filtersets.py:791 dcim/filtersets.py:813 dcim/filtersets.py:963 +#: virtualization/filtersets.py:234 +msgid "Config template (ID)" +msgstr "" + +#: dcim/filtersets.py:837 +msgid "Device type (slug)" +msgstr "" + +#: dcim/filtersets.py:857 +msgid "Parent Device (ID)" +msgstr "" + +#: dcim/filtersets.py:861 virtualization/filtersets.py:216 +msgid "Platform (ID)" +msgstr "" + +#: dcim/filtersets.py:867 extras/filtersets.py:443 +#: virtualization/filtersets.py:222 +msgid "Platform (slug)" +msgstr "" + +#: dcim/filtersets.py:903 dcim/filtersets.py:1210 dcim/filtersets.py:1705 +#: dcim/filtersets.py:1877 dcim/filtersets.py:1935 +msgid "Site name (slug)" +msgstr "" + +#: dcim/filtersets.py:918 +msgid "VM cluster (ID)" +msgstr "" + +#: dcim/filtersets.py:924 +msgid "Device model (slug)" +msgstr "" + +#: dcim/filtersets.py:935 dcim/forms/bulk_edit.py:421 +msgid "Is full depth" +msgstr "" + +#: dcim/filtersets.py:939 dcim/forms/common.py:18 dcim/forms/filtersets.py:738 +#: dcim/forms/filtersets.py:1276 dcim/models/device_components.py:520 +#: virtualization/filtersets.py:226 virtualization/filtersets.py:292 +#: virtualization/forms/filtersets.py:165 +#: virtualization/forms/filtersets.py:211 +msgid "MAC address" +msgstr "" + +#: dcim/filtersets.py:946 dcim/forms/filtersets.py:747 +#: dcim/forms/filtersets.py:834 virtualization/filtersets.py:230 +#: virtualization/forms/filtersets.py:169 +msgid "Has a primary IP" +msgstr "" + +#: dcim/filtersets.py:950 +msgid "Has an out-of-band IP" +msgstr "" + +#: dcim/filtersets.py:955 +msgid "Virtual chassis (ID)" +msgstr "" + +#: dcim/filtersets.py:959 +msgid "Is a virtual chassis member" +msgstr "" + +#: dcim/filtersets.py:1000 +msgid "Primary IPv4 (ID)" +msgstr "" + +#: dcim/filtersets.py:1005 +msgid "Primary IPv6 (ID)" +msgstr "" + +#: dcim/filtersets.py:1010 +msgid "OOB IP (ID)" +msgstr "" + +#: dcim/filtersets.py:1135 +msgid "Module type (model)" +msgstr "" + +#: dcim/filtersets.py:1141 +msgid "Module Bay (ID)" +msgstr "" + +#: dcim/filtersets.py:1145 dcim/filtersets.py:1236 ipam/filtersets.py:567 +#: ipam/filtersets.py:802 ipam/filtersets.py:1010 ipam/filtersets.py:1143 +#: virtualization/filtersets.py:157 +msgid "Device (ID)" +msgstr "" + +#: dcim/filtersets.py:1232 +msgid "Rack (name)" +msgstr "" + +#: dcim/filtersets.py:1242 ipam/filtersets.py:562 ipam/filtersets.py:797 +#: ipam/filtersets.py:1016 ipam/filtersets.py:1138 +msgid "Device (name)" +msgstr "" + +#: dcim/filtersets.py:1253 +msgid "Device type (model)" +msgstr "" + +#: dcim/filtersets.py:1258 dcim/filtersets.py:1281 +msgid "Device role (ID)" +msgstr "" + +#: dcim/filtersets.py:1264 dcim/filtersets.py:1287 +msgid "Device role (slug)" +msgstr "" + +#: dcim/filtersets.py:1269 +msgid "Virtual Chassis (ID)" +msgstr "" + +#: dcim/filtersets.py:1275 dcim/forms/filtersets.py:105 +#: dcim/tables/devices.py:235 netbox/navigation/menu.py:67 +#: templates/dcim/device.html:140 templates/dcim/device_edit.html:93 +#: templates/dcim/virtualchassis.html:20 +#: templates/dcim/virtualchassis_add.html:8 +#: templates/dcim/virtualchassis_edit.html:25 +msgid "Virtual Chassis" +msgstr "" + +#: dcim/filtersets.py:1307 +msgid "Module (ID)" +msgstr "" + +#: dcim/filtersets.py:1411 ipam/forms/bulk_import.py:191 +#: ipam/forms/bulk_import.py:568 +msgid "Assigned VLAN" +msgstr "" + +#: dcim/filtersets.py:1415 +msgid "Assigned VID" +msgstr "" + +#: dcim/filtersets.py:1420 dcim/forms/bulk_edit.py:1374 +#: dcim/forms/bulk_import.py:828 dcim/forms/filtersets.py:1319 +#: dcim/forms/model_forms.py:1174 dcim/models/device_components.py:709 +#: dcim/tables/devices.py:625 ipam/filtersets.py:281 ipam/filtersets.py:292 +#: ipam/filtersets.py:448 ipam/filtersets.py:540 ipam/filtersets.py:551 +#: ipam/forms/bulk_edit.py:228 ipam/forms/bulk_edit.py:283 +#: ipam/forms/bulk_edit.py:325 ipam/forms/bulk_import.py:159 +#: ipam/forms/bulk_import.py:245 ipam/forms/bulk_import.py:281 +#: ipam/forms/filtersets.py:70 ipam/forms/filtersets.py:171 +#: ipam/forms/filtersets.py:299 ipam/forms/model_forms.py:61 +#: ipam/forms/model_forms.py:205 ipam/forms/model_forms.py:248 +#: ipam/forms/model_forms.py:292 ipam/forms/model_forms.py:414 +#: ipam/forms/model_forms.py:428 ipam/forms/model_forms.py:442 +#: ipam/models/ip.py:232 ipam/models/ip.py:511 ipam/models/ip.py:719 +#: ipam/models/vrfs.py:62 ipam/tables/ip.py:241 ipam/tables/ip.py:306 +#: ipam/tables/ip.py:356 ipam/tables/ip.py:445 +#: templates/dcim/interface.html:134 templates/ipam/ipaddress.html:21 +#: templates/ipam/iprange.html:43 templates/ipam/prefix.html:19 +#: templates/ipam/vrf.html:7 templates/ipam/vrf.html:14 +#: templates/virtualization/vminterface.html:50 +#: virtualization/forms/bulk_edit.py:258 +#: virtualization/forms/bulk_import.py:170 +#: virtualization/forms/filtersets.py:216 +#: virtualization/forms/model_forms.py:326 +#: virtualization/models/virtualmachines.py:286 +#: virtualization/tables/virtualmachines.py:118 +msgid "VRF" +msgstr "" + +#: dcim/filtersets.py:1426 ipam/filtersets.py:287 ipam/filtersets.py:298 +#: ipam/filtersets.py:454 ipam/filtersets.py:546 ipam/filtersets.py:557 +msgid "VRF (RD)" +msgstr "" + +#: dcim/filtersets.py:1431 ipam/filtersets.py:958 ipam/filtersets.py:1106 +msgid "L2VPN (ID)" +msgstr "" + +#: dcim/filtersets.py:1437 dcim/forms/filtersets.py:1324 +#: dcim/tables/devices.py:579 ipam/filtersets.py:964 +#: ipam/forms/bulk_import.py:540 ipam/forms/filtersets.py:501 +#: ipam/forms/filtersets.py:565 ipam/forms/model_forms.py:779 +#: ipam/forms/model_forms.py:797 ipam/models/l2vpn.py:63 +#: ipam/tables/l2vpn.py:55 ipam/tables/vlans.py:133 +#: templates/dcim/interface.html:109 templates/ipam/l2vpntermination.html:15 +#: templates/ipam/vlan.html:69 virtualization/forms/filtersets.py:221 +msgid "L2VPN" +msgstr "" + +#: dcim/filtersets.py:1469 +msgid "Virtual Chassis Interfaces for Device" +msgstr "" + +#: dcim/filtersets.py:1474 +msgid "Virtual Chassis Interfaces for Device (ID)" +msgstr "" + +#: dcim/filtersets.py:1478 +msgid "Kind of interface" +msgstr "" + +#: dcim/filtersets.py:1483 virtualization/filtersets.py:284 +msgid "Parent interface (ID)" +msgstr "" + +#: dcim/filtersets.py:1488 virtualization/filtersets.py:289 +msgid "Bridged interface (ID)" +msgstr "" + +#: dcim/filtersets.py:1493 +msgid "LAG interface (ID)" +msgstr "" + +#: dcim/filtersets.py:1662 +msgid "Master (ID)" +msgstr "" + +#: dcim/filtersets.py:1668 +msgid "Master (name)" +msgstr "" + +#: dcim/filtersets.py:1710 tenancy/filtersets.py:208 +msgid "Tenant (ID)" +msgstr "" + +#: dcim/filtersets.py:1716 extras/filtersets.py:492 tenancy/filtersets.py:214 +msgid "Tenant (slug)" +msgstr "" + +#: dcim/filtersets.py:1751 dcim/forms/filtersets.py:983 +msgid "Unterminated" +msgstr "" + +#: dcim/filtersets.py:1939 +msgid "Power panel (ID)" +msgstr "" + +#: dcim/forms/bulk_create.py:40 extras/forms/filtersets.py:385 +#: extras/forms/mixins.py:82 extras/forms/model_forms.py:341 +#: extras/forms/model_forms.py:392 netbox/forms/base.py:71 +#: netbox/tables/columns.py:448 +#: templates/circuits/inc/circuit_termination.html:119 +#: templates/generic/bulk_edit.html:81 templates/inc/panels/tags.html:5 +#: utilities/forms/fields/fields.py:81 +msgid "Tags" +msgstr "" + +#: dcim/forms/bulk_create.py:112 dcim/forms/filtersets.py:1381 +#: dcim/forms/model_forms.py:422 dcim/forms/model_forms.py:467 +#: dcim/forms/object_create.py:179 dcim/forms/object_create.py:319 +#: dcim/tables/devices.py:198 dcim/tables/devices.py:703 +#: dcim/tables/devicetypes.py:242 templates/dcim/device.html:62 +#: templates/dcim/device.html:146 templates/dcim/modulebay.html:35 +#: templates/dcim/virtualchassis.html:59 +#: templates/dcim/virtualchassis_edit.html:56 +msgid "Position" +msgstr "" + +#: dcim/forms/bulk_create.py:114 +msgid "" +"Alphanumeric ranges are supported. (Must match the number of names being " +"created.)" +msgstr "" + +#: dcim/forms/bulk_edit.py:115 dcim/forms/bulk_import.py:99 +#: dcim/forms/model_forms.py:120 dcim/tables/sites.py:89 ipam/filtersets.py:931 +#: ipam/forms/bulk_edit.py:530 ipam/forms/bulk_import.py:447 +#: ipam/forms/model_forms.py:511 ipam/tables/fhrp.py:67 +#: ipam/tables/vlans.py:118 ipam/tables/vlans.py:221 +#: templates/dcim/interface.html:290 templates/dcim/site.html:43 +#: templates/ipam/inc/panels/fhrp_groups.html:10 templates/ipam/vlan.html:30 +#: templates/tenancy/contact.html:22 templates/tenancy/tenant.html:21 +#: templates/users/group.html:6 templates/users/group.html:14 +#: templates/virtualization/cluster.html:32 +#: templates/wireless/wirelesslan.html:19 tenancy/forms/bulk_edit.py:42 +#: tenancy/forms/bulk_edit.py:93 tenancy/forms/bulk_import.py:40 +#: tenancy/forms/bulk_import.py:81 tenancy/forms/filtersets.py:48 +#: tenancy/forms/filtersets.py:78 tenancy/forms/filtersets.py:98 +#: tenancy/forms/model_forms.py:49 tenancy/forms/model_forms.py:105 +#: tenancy/forms/model_forms.py:127 tenancy/tables/contacts.py:60 +#: tenancy/tables/tenants.py:42 users/filtersets.py:42 users/filtersets.py:145 +#: users/forms/filtersets.py:34 users/forms/filtersets.py:40 +#: users/forms/filtersets.py:82 virtualization/forms/bulk_edit.py:62 +#: virtualization/forms/bulk_import.py:46 virtualization/forms/filtersets.py:81 +#: virtualization/forms/model_forms.py:68 virtualization/tables/clusters.py:70 +#: wireless/forms/bulk_edit.py:47 wireless/forms/bulk_import.py:36 +#: wireless/forms/filtersets.py:45 wireless/forms/model_forms.py:41 +#: wireless/tables/wirelesslan.py:48 +msgid "Group" +msgstr "" + +#: dcim/forms/bulk_edit.py:130 +msgid "Contact name" +msgstr "" + +#: dcim/forms/bulk_edit.py:135 +msgid "Contact phone" +msgstr "" + +#: dcim/forms/bulk_edit.py:141 +msgid "Contact E-mail" +msgstr "" + +#: dcim/forms/bulk_edit.py:144 dcim/forms/bulk_import.py:122 +#: dcim/forms/model_forms.py:131 +msgid "Time zone" +msgstr "" + +#: dcim/forms/bulk_edit.py:266 dcim/forms/bulk_edit.py:1152 +#: dcim/forms/bulk_edit.py:1539 dcim/forms/bulk_import.py:199 +#: dcim/forms/bulk_import.py:1009 dcim/forms/filtersets.py:296 +#: dcim/forms/filtersets.py:697 dcim/forms/filtersets.py:1408 +#: dcim/forms/model_forms.py:224 dcim/forms/model_forms.py:962 +#: dcim/forms/model_forms.py:1303 dcim/forms/object_import.py:186 +#: dcim/tables/devices.py:202 dcim/tables/devices.py:811 +#: dcim/tables/devices.py:922 dcim/tables/devicetypes.py:300 +#: dcim/tables/racks.py:69 extras/filtersets.py:426 ipam/forms/bulk_edit.py:247 +#: ipam/forms/bulk_edit.py:296 ipam/forms/bulk_edit.py:344 +#: ipam/forms/bulk_edit.py:548 ipam/forms/bulk_import.py:199 +#: ipam/forms/bulk_import.py:264 ipam/forms/bulk_import.py:300 +#: ipam/forms/bulk_import.py:466 ipam/forms/filtersets.py:236 +#: ipam/forms/filtersets.py:282 ipam/forms/filtersets.py:349 +#: ipam/forms/filtersets.py:492 ipam/forms/model_forms.py:189 +#: ipam/forms/model_forms.py:224 ipam/forms/model_forms.py:251 +#: ipam/forms/model_forms.py:649 ipam/tables/ip.py:257 ipam/tables/ip.py:313 +#: ipam/tables/ip.py:363 ipam/tables/vlans.py:126 ipam/tables/vlans.py:230 +#: templates/dcim/device.html:204 +#: templates/dcim/inc/panels/inventory_items.html:12 +#: templates/dcim/interface.html:227 templates/dcim/inventoryitem.html:37 +#: templates/dcim/rack.html:57 templates/ipam/ipaddress.html:44 +#: templates/ipam/iprange.html:53 templates/ipam/prefix.html:78 +#: templates/ipam/role.html:20 templates/ipam/vlan.html:55 +#: templates/virtualization/virtualmachine.html:26 +#: templates/wireless/inc/wirelesslink_interface.html:20 +#: tenancy/forms/bulk_edit.py:141 tenancy/forms/filtersets.py:108 +#: tenancy/forms/model_forms.py:142 tenancy/tables/contacts.py:102 +#: virtualization/forms/bulk_edit.py:142 +#: virtualization/forms/bulk_import.py:105 +#: virtualization/forms/filtersets.py:150 +#: virtualization/forms/model_forms.py:197 +#: virtualization/tables/virtualmachines.py:63 +msgid "Role" +msgstr "" + +#: dcim/forms/bulk_edit.py:273 dcim/forms/bulk_edit.py:605 +#: dcim/forms/bulk_edit.py:654 templates/dcim/device.html:123 +#: templates/dcim/module.html:75 templates/dcim/modulebay.html:69 +#: templates/dcim/rack.html:65 +msgid "Serial Number" +msgstr "" + +#: dcim/forms/bulk_edit.py:276 dcim/forms/filtersets.py:303 +#: dcim/forms/filtersets.py:733 dcim/forms/filtersets.py:873 +#: dcim/forms/filtersets.py:1421 +msgid "Asset tag" +msgstr "" + +#: dcim/forms/bulk_edit.py:286 dcim/forms/bulk_import.py:212 +#: dcim/forms/filtersets.py:288 templates/dcim/rack.html:98 +#: templates/dcim/rack_edit.html:48 +msgid "Width" +msgstr "" + +#: dcim/forms/bulk_edit.py:292 +msgid "Height (U)" +msgstr "" + +#: dcim/forms/bulk_edit.py:297 +msgid "Descending units" +msgstr "" + +#: dcim/forms/bulk_edit.py:300 +msgid "Outer width" +msgstr "" + +#: dcim/forms/bulk_edit.py:305 +msgid "Outer depth" +msgstr "" + +#: dcim/forms/bulk_edit.py:310 dcim/forms/bulk_import.py:217 +msgid "Outer unit" +msgstr "" + +#: dcim/forms/bulk_edit.py:315 +msgid "Mounting depth" +msgstr "" + +#: dcim/forms/bulk_edit.py:320 dcim/forms/bulk_edit.py:349 +#: dcim/forms/bulk_edit.py:434 dcim/forms/bulk_edit.py:457 +#: dcim/forms/bulk_edit.py:473 dcim/forms/bulk_edit.py:493 +#: dcim/forms/bulk_import.py:324 dcim/forms/bulk_import.py:350 +#: dcim/forms/filtersets.py:248 dcim/forms/filtersets.py:308 +#: dcim/forms/filtersets.py:332 dcim/forms/filtersets.py:420 +#: dcim/forms/filtersets.py:525 dcim/forms/filtersets.py:544 +#: dcim/forms/filtersets.py:600 dcim/forms/model_forms.py:337 +#: dcim/tables/devicetypes.py:103 dcim/tables/modules.py:35 +#: dcim/tables/racks.py:103 extras/forms/bulk_edit.py:44 +#: extras/forms/bulk_edit.py:102 extras/forms/bulk_edit.py:152 +#: extras/forms/bulk_edit.py:256 extras/forms/filtersets.py:62 +#: extras/forms/filtersets.py:130 extras/forms/filtersets.py:217 +#: ipam/forms/bulk_edit.py:189 templates/dcim/device.html:346 +#: templates/dcim/devicetype.html:52 templates/dcim/moduletype.html:31 +#: templates/dcim/rack_edit.html:60 templates/dcim/rack_edit.html:63 +#: templates/extras/configcontext.html:18 templates/extras/customlink.html:26 +#: templates/extras/savedfilter.html:34 templates/ipam/role.html:33 +msgid "Weight" +msgstr "" + +#: dcim/forms/bulk_edit.py:325 dcim/forms/filtersets.py:313 +msgid "Max weight" +msgstr "" + +#: dcim/forms/bulk_edit.py:330 dcim/forms/bulk_edit.py:439 +#: dcim/forms/bulk_edit.py:478 dcim/forms/bulk_import.py:223 +#: dcim/forms/bulk_import.py:329 dcim/forms/bulk_import.py:355 +#: dcim/forms/filtersets.py:318 dcim/forms/filtersets.py:529 +#: dcim/forms/filtersets.py:604 +msgid "Weight unit" +msgstr "" + +#: dcim/forms/bulk_edit.py:344 dcim/forms/bulk_edit.py:800 +#: dcim/forms/bulk_import.py:262 dcim/forms/bulk_import.py:265 +#: dcim/forms/bulk_import.py:490 dcim/forms/bulk_import.py:1286 +#: dcim/forms/bulk_import.py:1290 dcim/forms/filtersets.py:100 +#: dcim/forms/filtersets.py:336 dcim/forms/filtersets.py:350 +#: dcim/forms/filtersets.py:388 dcim/forms/filtersets.py:692 +#: dcim/forms/filtersets.py:941 dcim/forms/filtersets.py:1072 +#: dcim/forms/model_forms.py:241 dcim/forms/model_forms.py:413 +#: dcim/forms/model_forms.py:661 dcim/forms/object_create.py:366 +#: dcim/tables/devices.py:194 dcim/tables/power.py:70 dcim/tables/racks.py:148 +#: ipam/forms/bulk_edit.py:466 ipam/forms/filtersets.py:430 +#: ipam/forms/model_forms.py:573 templates/dcim/device.html:47 +#: templates/dcim/inc/cable_termination.html:16 +#: templates/dcim/powerfeed.html:31 templates/dcim/rack.html:13 +#: templates/dcim/rack/base.html:4 templates/dcim/rack_edit.html:8 +#: templates/dcim/rackreservation.html:19 +#: templates/dcim/rackreservation.html:38 +#: virtualization/forms/model_forms.py:115 +msgid "Rack" +msgstr "" + +#: dcim/forms/bulk_edit.py:346 dcim/forms/bulk_edit.py:623 +#: dcim/forms/filtersets.py:245 dcim/forms/filtersets.py:329 +#: dcim/forms/filtersets.py:414 dcim/forms/filtersets.py:539 +#: dcim/forms/filtersets.py:646 dcim/forms/filtersets.py:846 +#: dcim/forms/model_forms.py:588 dcim/forms/model_forms.py:1373 +#: templates/dcim/device_edit.html:20 templates/dcim/inventoryitem_edit.html:23 +msgid "Hardware" +msgstr "" + +#: dcim/forms/bulk_edit.py:400 dcim/forms/bulk_edit.py:464 +#: dcim/forms/bulk_edit.py:528 dcim/forms/bulk_edit.py:552 +#: dcim/forms/bulk_edit.py:633 dcim/forms/bulk_edit.py:1157 +#: dcim/forms/bulk_edit.py:1544 dcim/forms/bulk_import.py:311 +#: dcim/forms/bulk_import.py:345 dcim/forms/bulk_import.py:387 +#: dcim/forms/bulk_import.py:423 dcim/forms/bulk_import.py:1015 +#: dcim/forms/filtersets.py:425 dcim/forms/filtersets.py:549 +#: dcim/forms/filtersets.py:625 dcim/forms/filtersets.py:702 +#: dcim/forms/filtersets.py:851 dcim/forms/filtersets.py:1414 +#: dcim/forms/model_forms.py:274 dcim/forms/model_forms.py:288 +#: dcim/forms/model_forms.py:330 dcim/forms/model_forms.py:370 +#: dcim/forms/model_forms.py:967 dcim/forms/model_forms.py:1308 +#: dcim/forms/object_import.py:192 dcim/tables/devices.py:129 +#: dcim/tables/devices.py:205 dcim/tables/devices.py:925 +#: dcim/tables/devicetypes.py:81 dcim/tables/devicetypes.py:304 +#: dcim/tables/modules.py:20 dcim/tables/modules.py:60 +#: templates/dcim/devicetype.html:17 templates/dcim/inventoryitem.html:45 +#: templates/dcim/manufacturer.html:34 templates/dcim/modulebay.html:61 +#: templates/dcim/moduletype.html:15 templates/dcim/platform.html:40 +msgid "Manufacturer" +msgstr "" + +#: dcim/forms/bulk_edit.py:405 dcim/forms/bulk_import.py:317 +#: dcim/forms/filtersets.py:430 dcim/forms/model_forms.py:292 +msgid "Default platform" +msgstr "" + +#: dcim/forms/bulk_edit.py:410 dcim/forms/bulk_edit.py:469 +#: dcim/forms/filtersets.py:433 dcim/forms/filtersets.py:553 +msgid "Part number" +msgstr "" + +#: dcim/forms/bulk_edit.py:414 +msgid "U height" +msgstr "" + +#: dcim/forms/bulk_edit.py:426 +msgid "Exclude from utilization" +msgstr "" + +#: dcim/forms/bulk_edit.py:429 dcim/forms/bulk_edit.py:598 +#: dcim/forms/bulk_import.py:517 dcim/forms/filtersets.py:442 +#: dcim/forms/filtersets.py:724 templates/dcim/device.html:117 +#: templates/dcim/devicetype.html:68 +msgid "Airflow" +msgstr "" + +#: dcim/forms/bulk_edit.py:453 dcim/forms/model_forms.py:303 +#: dcim/tables/devicetypes.py:78 templates/dcim/device.html:107 +#: templates/dcim/devicebay.html:59 templates/dcim/module.html:59 +msgid "Device Type" +msgstr "" + +#: dcim/forms/bulk_edit.py:492 dcim/forms/model_forms.py:336 +#: dcim/tables/modules.py:17 dcim/tables/modules.py:65 +#: templates/dcim/module.html:63 templates/dcim/modulebay.html:65 +#: templates/dcim/moduletype.html:11 +msgid "Module Type" +msgstr "" + +#: dcim/forms/bulk_edit.py:506 dcim/models/devices.py:472 +msgid "VM role" +msgstr "" + +#: dcim/forms/bulk_edit.py:509 dcim/forms/bulk_edit.py:533 +#: dcim/forms/bulk_edit.py:613 dcim/forms/bulk_import.py:368 +#: dcim/forms/bulk_import.py:372 dcim/forms/bulk_import.py:394 +#: dcim/forms/bulk_import.py:398 dcim/forms/bulk_import.py:523 +#: dcim/forms/bulk_import.py:527 dcim/forms/filtersets.py:615 +#: dcim/forms/filtersets.py:630 dcim/forms/filtersets.py:743 +#: dcim/forms/model_forms.py:349 dcim/forms/model_forms.py:375 +#: dcim/forms/model_forms.py:476 virtualization/forms/bulk_import.py:131 +#: virtualization/forms/bulk_import.py:132 +#: virtualization/forms/filtersets.py:177 +#: virtualization/forms/model_forms.py:216 +msgid "Config template" +msgstr "" + +#: dcim/forms/bulk_edit.py:557 dcim/forms/bulk_edit.py:951 +#: dcim/forms/bulk_import.py:429 dcim/forms/filtersets.py:110 +#: dcim/forms/model_forms.py:435 dcim/forms/model_forms.py:775 +#: dcim/forms/model_forms.py:789 extras/filtersets.py:421 +msgid "Device type" +msgstr "" + +#: dcim/forms/bulk_edit.py:565 dcim/forms/bulk_import.py:410 +#: dcim/forms/filtersets.py:115 dcim/forms/model_forms.py:440 +msgid "Device role" +msgstr "" + +#: dcim/forms/bulk_edit.py:588 dcim/forms/bulk_import.py:435 +#: dcim/forms/filtersets.py:716 dcim/forms/model_forms.py:385 +#: dcim/forms/model_forms.py:444 extras/filtersets.py:437 +#: templates/dcim/device.html:208 templates/dcim/platform.html:27 +#: templates/virtualization/virtualmachine.html:30 +#: virtualization/forms/bulk_edit.py:157 +#: virtualization/forms/bulk_import.py:121 +#: virtualization/forms/filtersets.py:161 +#: virtualization/forms/model_forms.py:205 +msgid "Platform" +msgstr "" + +#: dcim/forms/bulk_edit.py:621 dcim/forms/bulk_edit.py:1171 +#: dcim/forms/bulk_edit.py:1534 dcim/forms/bulk_edit.py:1580 +#: dcim/forms/bulk_import.py:578 dcim/forms/bulk_import.py:640 +#: dcim/forms/bulk_import.py:666 dcim/forms/bulk_import.py:692 +#: dcim/forms/bulk_import.py:712 dcim/forms/bulk_import.py:765 +#: dcim/forms/bulk_import.py:879 dcim/forms/bulk_import.py:927 +#: dcim/forms/bulk_import.py:944 dcim/forms/bulk_import.py:956 +#: dcim/forms/bulk_import.py:1004 dcim/forms/bulk_import.py:1350 +#: dcim/forms/connections.py:23 dcim/forms/filtersets.py:127 +#: dcim/forms/filtersets.py:824 dcim/forms/filtersets.py:957 +#: dcim/forms/filtersets.py:1146 dcim/forms/filtersets.py:1168 +#: dcim/forms/filtersets.py:1190 dcim/forms/filtersets.py:1207 +#: dcim/forms/filtersets.py:1227 dcim/forms/filtersets.py:1334 +#: dcim/forms/filtersets.py:1356 dcim/forms/filtersets.py:1377 +#: dcim/forms/filtersets.py:1392 dcim/forms/filtersets.py:1403 +#: dcim/forms/filtersets.py:1467 dcim/forms/filtersets.py:1491 +#: dcim/forms/filtersets.py:1515 dcim/forms/model_forms.py:554 +#: dcim/forms/model_forms.py:752 dcim/forms/model_forms.py:1003 +#: dcim/forms/model_forms.py:1452 dcim/forms/object_create.py:239 +#: dcim/tables/connections.py:22 dcim/tables/connections.py:41 +#: dcim/tables/connections.py:60 dcim/tables/devices.py:314 +#: dcim/tables/devices.py:374 dcim/tables/devices.py:418 +#: dcim/tables/devices.py:463 dcim/tables/devices.py:511 +#: dcim/tables/devices.py:597 dcim/tables/devices.py:693 +#: dcim/tables/devices.py:753 dcim/tables/devices.py:803 +#: dcim/tables/devices.py:863 dcim/tables/devices.py:915 +#: dcim/tables/devices.py:1037 dcim/tables/modules.py:52 +#: extras/forms/filtersets.py:304 ipam/forms/bulk_import.py:306 +#: ipam/forms/bulk_import.py:492 ipam/forms/bulk_import.py:543 +#: ipam/forms/filtersets.py:594 ipam/forms/model_forms.py:687 +#: ipam/tables/vlans.py:176 templates/dcim/consoleport.html:23 +#: templates/dcim/consoleserverport.html:23 templates/dcim/device.html:13 +#: templates/dcim/device.html:145 templates/dcim/device_edit.html:10 +#: templates/dcim/devicebay.html:23 templates/dcim/devicebay.html:55 +#: templates/dcim/frontport.html:23 templates/dcim/interface.html:31 +#: templates/dcim/interface.html:163 templates/dcim/inventoryitem.html:21 +#: templates/dcim/module.html:55 templates/dcim/modulebay.html:21 +#: templates/dcim/poweroutlet.html:23 templates/dcim/powerport.html:23 +#: templates/dcim/rearport.html:23 templates/dcim/virtualchassis.html:58 +#: templates/dcim/virtualchassis_edit.html:52 +#: templates/dcim/virtualdevicecontext.html:25 +#: templates/ipam/ipaddress_edit.html:42 +#: templates/ipam/l2vpntermination_edit.html:22 +#: templates/ipam/service_create.html:17 templates/ipam/service_edit.html:16 +#: templates/virtualization/virtualmachine.html:115 +#: templates/wireless/inc/wirelesslink_interface.html:6 +#: virtualization/filtersets.py:163 virtualization/forms/bulk_edit.py:134 +#: virtualization/forms/bulk_import.py:98 +#: virtualization/forms/filtersets.py:121 +#: virtualization/forms/model_forms.py:187 +#: virtualization/tables/virtualmachines.py:59 +#: wireless/forms/model_forms.py:100 wireless/forms/model_forms.py:140 +#: wireless/tables/wirelesslan.py:75 +msgid "Device" +msgstr "" + +#: dcim/forms/bulk_edit.py:624 netbox/navigation/menu.py:421 +#: templates/extras/dashboard/widget_config.html:7 +msgid "Configuration" +msgstr "" + +#: dcim/forms/bulk_edit.py:638 dcim/forms/bulk_import.py:590 +#: dcim/forms/model_forms.py:568 dcim/forms/model_forms.py:794 +msgid "Module type" +msgstr "" + +#: dcim/forms/bulk_edit.py:689 dcim/forms/bulk_edit.py:874 +#: dcim/forms/bulk_edit.py:893 dcim/forms/bulk_edit.py:916 +#: dcim/forms/bulk_edit.py:958 dcim/forms/bulk_edit.py:1002 +#: dcim/forms/bulk_edit.py:1053 dcim/forms/bulk_edit.py:1080 +#: dcim/forms/bulk_edit.py:1107 dcim/forms/bulk_edit.py:1125 +#: dcim/forms/bulk_edit.py:1143 dcim/forms/filtersets.py:63 +#: dcim/forms/object_create.py:45 templates/dcim/cable.html:33 +#: templates/dcim/consoleport.html:35 templates/dcim/consoleserverport.html:35 +#: templates/dcim/devicebay.html:31 templates/dcim/frontport.html:35 +#: templates/dcim/inc/panels/inventory_items.html:11 +#: templates/dcim/interface.html:43 templates/dcim/inventoryitem.html:33 +#: templates/dcim/modulebay.html:31 templates/dcim/poweroutlet.html:35 +#: templates/dcim/powerport.html:35 templates/dcim/rearport.html:35 +#: templates/extras/customfield.html:27 templates/generic/bulk_import.html:155 +msgid "Label" +msgstr "" + +#: dcim/forms/bulk_edit.py:698 dcim/forms/filtersets.py:974 +#: templates/dcim/cable.html:51 +msgid "Length" +msgstr "" + +#: dcim/forms/bulk_edit.py:703 dcim/forms/bulk_import.py:1158 +#: dcim/forms/bulk_import.py:1161 dcim/forms/filtersets.py:978 +msgid "Length unit" +msgstr "" + +#: dcim/forms/bulk_edit.py:727 templates/dcim/virtualchassis.html:24 +msgid "Domain" +msgstr "" + +#: dcim/forms/bulk_edit.py:795 dcim/forms/bulk_import.py:1273 +#: dcim/forms/filtersets.py:1063 dcim/forms/model_forms.py:656 +msgid "Power panel" +msgstr "" + +#: dcim/forms/bulk_edit.py:817 dcim/forms/bulk_import.py:1309 +#: dcim/forms/filtersets.py:1085 templates/dcim/powerfeed.html:90 +msgid "Supply" +msgstr "" + +#: dcim/forms/bulk_edit.py:823 dcim/forms/bulk_import.py:1314 +#: dcim/forms/filtersets.py:1090 templates/dcim/powerfeed.html:102 +msgid "Phase" +msgstr "" + +#: dcim/forms/bulk_edit.py:829 dcim/forms/filtersets.py:1095 +#: templates/dcim/powerfeed.html:94 +msgid "Voltage" +msgstr "" + +#: dcim/forms/bulk_edit.py:833 dcim/forms/filtersets.py:1099 +#: templates/dcim/powerfeed.html:98 +msgid "Amperage" +msgstr "" + +#: dcim/forms/bulk_edit.py:837 dcim/forms/filtersets.py:1103 +msgid "Max utilization" +msgstr "" + +#: dcim/forms/bulk_edit.py:841 dcim/forms/bulk_edit.py:1200 +#: dcim/forms/bulk_edit.py:1217 dcim/forms/bulk_edit.py:1234 +#: dcim/forms/bulk_edit.py:1252 dcim/forms/bulk_edit.py:1340 +#: dcim/forms/bulk_edit.py:1478 dcim/forms/bulk_edit.py:1495 +msgid "Mark connected" +msgstr "" + +#: dcim/forms/bulk_edit.py:926 +msgid "Maximum draw" +msgstr "" + +#: dcim/forms/bulk_edit.py:929 dcim/models/device_component_templates.py:257 +#: dcim/models/device_components.py:358 +msgid "Maximum power draw (watts)" +msgstr "" + +#: dcim/forms/bulk_edit.py:932 +msgid "Allocated draw" +msgstr "" + +#: dcim/forms/bulk_edit.py:935 dcim/models/device_component_templates.py:264 +#: dcim/models/device_components.py:365 +msgid "Allocated power draw (watts)" +msgstr "" + +#: dcim/forms/bulk_edit.py:968 dcim/forms/bulk_import.py:723 +#: dcim/forms/model_forms.py:847 dcim/forms/model_forms.py:1075 +#: dcim/forms/model_forms.py:1360 dcim/forms/object_import.py:60 +msgid "Power port" +msgstr "" + +#: dcim/forms/bulk_edit.py:973 +msgid "Feed leg" +msgstr "" + +#: dcim/forms/bulk_edit.py:1019 dcim/forms/bulk_edit.py:1325 +msgid "Management only" +msgstr "" + +#: dcim/forms/bulk_edit.py:1029 dcim/forms/bulk_edit.py:1331 +#: dcim/forms/bulk_import.py:813 dcim/forms/filtersets.py:1285 +#: dcim/forms/object_import.py:95 dcim/models/device_component_templates.py:412 +#: dcim/models/device_components.py:668 +msgid "PoE mode" +msgstr "" + +#: dcim/forms/bulk_edit.py:1035 dcim/forms/bulk_edit.py:1337 +#: dcim/forms/bulk_import.py:819 dcim/forms/filtersets.py:1290 +#: dcim/forms/object_import.py:100 +#: dcim/models/device_component_templates.py:418 +#: dcim/models/device_components.py:674 +msgid "PoE type" +msgstr "" + +#: dcim/forms/bulk_edit.py:1041 dcim/forms/filtersets.py:1295 +#: dcim/forms/object_import.py:105 +msgid "Wireless role" +msgstr "" + +#: dcim/forms/bulk_edit.py:1178 dcim/forms/model_forms.py:587 +#: dcim/forms/model_forms.py:1018 dcim/tables/devices.py:337 +#: templates/dcim/consoleport.html:27 templates/dcim/consoleserverport.html:27 +#: templates/dcim/frontport.html:27 templates/dcim/interface.html:35 +#: templates/dcim/module.html:51 templates/dcim/modulebay.html:57 +#: templates/dcim/poweroutlet.html:27 templates/dcim/powerport.html:27 +#: templates/dcim/rearport.html:27 +msgid "Module" +msgstr "" + +#: dcim/forms/bulk_edit.py:1305 dcim/tables/devices.py:663 +#: templates/dcim/interface.html:105 +msgid "LAG" +msgstr "" + +#: dcim/forms/bulk_edit.py:1310 dcim/forms/model_forms.py:1102 +msgid "Virtual device contexts" +msgstr "" + +#: dcim/forms/bulk_edit.py:1316 dcim/forms/bulk_import.py:651 +#: dcim/forms/bulk_import.py:677 dcim/forms/filtersets.py:1155 +#: dcim/forms/filtersets.py:1177 dcim/forms/filtersets.py:1249 +#: dcim/tables/devices.py:609 +#: templates/circuits/inc/circuit_termination.html:94 +#: templates/dcim/consoleport.html:43 templates/dcim/consoleserverport.html:43 +msgid "Speed" +msgstr "" + +#: dcim/forms/bulk_edit.py:1345 dcim/forms/bulk_import.py:822 +#: virtualization/forms/bulk_edit.py:230 +#: virtualization/forms/bulk_import.py:164 +msgid "Mode" +msgstr "" + +#: dcim/forms/bulk_edit.py:1353 dcim/forms/model_forms.py:1151 +#: ipam/forms/bulk_import.py:180 ipam/forms/filtersets.py:481 +#: ipam/models/vlans.py:82 virtualization/forms/bulk_edit.py:237 +#: virtualization/forms/model_forms.py:303 +msgid "VLAN group" +msgstr "" + +#: dcim/forms/bulk_edit.py:1361 dcim/forms/model_forms.py:1156 +#: dcim/tables/devices.py:582 virtualization/forms/bulk_edit.py:245 +#: virtualization/forms/model_forms.py:308 +msgid "Untagged VLAN" +msgstr "" + +#: dcim/forms/bulk_edit.py:1369 dcim/forms/model_forms.py:1165 +#: dcim/tables/devices.py:588 virtualization/forms/bulk_edit.py:253 +#: virtualization/forms/model_forms.py:317 +msgid "Tagged VLANs" +msgstr "" + +#: dcim/forms/bulk_edit.py:1379 dcim/forms/model_forms.py:1138 +msgid "Wireless LAN group" +msgstr "" + +#: dcim/forms/bulk_edit.py:1384 dcim/forms/model_forms.py:1143 +#: dcim/tables/devices.py:618 netbox/navigation/menu.py:134 +#: templates/dcim/interface.html:285 wireless/tables/wirelesslan.py:24 +msgid "Wireless LANs" +msgstr "" + +#: dcim/forms/bulk_edit.py:1393 dcim/forms/filtersets.py:1223 +#: dcim/forms/model_forms.py:1184 ipam/forms/bulk_edit.py:272 +#: ipam/forms/bulk_edit.py:363 ipam/forms/filtersets.py:170 +#: templates/dcim/interface.html:122 templates/ipam/prefix.html:96 +#: virtualization/forms/model_forms.py:331 +msgid "Addressing" +msgstr "" + +#: dcim/forms/bulk_edit.py:1394 dcim/forms/filtersets.py:645 +#: dcim/forms/model_forms.py:1185 virtualization/forms/model_forms.py:332 +msgid "Operation" +msgstr "" + +#: dcim/forms/bulk_edit.py:1395 dcim/forms/filtersets.py:1224 +#: dcim/forms/model_forms.py:879 dcim/forms/model_forms.py:1187 +msgid "PoE" +msgstr "" + +#: dcim/forms/bulk_edit.py:1396 dcim/forms/model_forms.py:1186 +#: templates/dcim/interface.html:93 virtualization/forms/bulk_edit.py:264 +#: virtualization/forms/model_forms.py:333 +msgid "Related Interfaces" +msgstr "" + +#: dcim/forms/bulk_edit.py:1397 dcim/forms/model_forms.py:1188 +#: virtualization/forms/bulk_edit.py:265 +#: virtualization/forms/model_forms.py:334 +msgid "802.1Q Switching" +msgstr "" + +#: dcim/forms/bulk_edit.py:1458 dcim/forms/bulk_edit.py:1460 +msgid "Interface mode must be specified to assign VLANs" +msgstr "" + +#: dcim/forms/bulk_edit.py:1465 dcim/forms/common.py:50 +msgid "An access interface cannot have tagged VLANs assigned." +msgstr "" + +#: dcim/forms/bulk_import.py:63 +msgid "Name of parent region" +msgstr "" + +#: dcim/forms/bulk_import.py:77 +msgid "Name of parent site group" +msgstr "" + +#: dcim/forms/bulk_import.py:96 +msgid "Assigned region" +msgstr "" + +#: dcim/forms/bulk_import.py:103 tenancy/forms/bulk_import.py:44 +#: tenancy/forms/bulk_import.py:85 wireless/forms/bulk_import.py:40 +msgid "Assigned group" +msgstr "" + +#: dcim/forms/bulk_import.py:122 +msgid "available options" +msgstr "" + +#: dcim/forms/bulk_import.py:133 dcim/forms/bulk_import.py:480 +#: dcim/forms/bulk_import.py:1270 ipam/forms/bulk_import.py:177 +#: ipam/forms/bulk_import.py:444 virtualization/forms/bulk_import.py:62 +#: virtualization/forms/bulk_import.py:88 +msgid "Assigned site" +msgstr "" + +#: dcim/forms/bulk_import.py:140 +msgid "Parent location" +msgstr "" + +#: dcim/forms/bulk_import.py:142 +msgid "Location not found." +msgstr "" + +#: dcim/forms/bulk_import.py:191 +msgid "Name of assigned tenant" +msgstr "" + +#: dcim/forms/bulk_import.py:203 +msgid "Name of assigned role" +msgstr "" + +#: dcim/forms/bulk_import.py:209 +msgid "Rack type" +msgstr "" + +#: dcim/forms/bulk_import.py:214 +msgid "Rail-to-rail width (in inches)" +msgstr "" + +#: dcim/forms/bulk_import.py:220 +msgid "Unit for outer dimensions" +msgstr "" + +#: dcim/forms/bulk_import.py:226 +msgid "Unit for rack weights" +msgstr "" + +#: dcim/forms/bulk_import.py:252 +msgid "Parent site" +msgstr "" + +#: dcim/forms/bulk_import.py:259 dcim/forms/bulk_import.py:1283 +msgid "Rack's location (if any)" +msgstr "" + +#: dcim/forms/bulk_import.py:268 dcim/forms/model_forms.py:246 +#: dcim/tables/racks.py:153 templates/dcim/rackreservation.html:11 +#: templates/dcim/rackreservation.html:52 +msgid "Units" +msgstr "" + +#: dcim/forms/bulk_import.py:271 +msgid "Comma-separated list of individual unit numbers" +msgstr "" + +#: dcim/forms/bulk_import.py:314 +msgid "The manufacturer which produces this device type" +msgstr "" + +#: dcim/forms/bulk_import.py:321 +msgid "The default platform for devices of this type (optional)" +msgstr "" + +#: dcim/forms/bulk_import.py:326 +msgid "Device weight" +msgstr "" + +#: dcim/forms/bulk_import.py:332 +msgid "Unit for device weight" +msgstr "" + +#: dcim/forms/bulk_import.py:352 +msgid "Module weight" +msgstr "" + +#: dcim/forms/bulk_import.py:358 +msgid "Unit for module weight" +msgstr "" + +#: dcim/forms/bulk_import.py:391 +msgid "Limit platform assignments to this manufacturer" +msgstr "" + +#: dcim/forms/bulk_import.py:413 tenancy/forms/bulk_import.py:106 +msgid "Assigned role" +msgstr "" + +#: dcim/forms/bulk_import.py:426 +msgid "Device type manufacturer" +msgstr "" + +#: dcim/forms/bulk_import.py:432 +msgid "Device type model" +msgstr "" + +#: dcim/forms/bulk_import.py:439 virtualization/forms/bulk_import.py:125 +msgid "Assigned platform" +msgstr "" + +#: dcim/forms/bulk_import.py:447 dcim/forms/bulk_import.py:451 +#: dcim/forms/model_forms.py:460 +msgid "Virtual chassis" +msgstr "" + +#: dcim/forms/bulk_import.py:454 dcim/forms/model_forms.py:449 +#: dcim/tables/devices.py:231 extras/filtersets.py:470 +#: extras/forms/filtersets.py:305 ipam/forms/bulk_edit.py:480 +#: ipam/forms/model_forms.py:590 templates/dcim/device.html:256 +#: templates/virtualization/cluster.html:11 +#: templates/virtualization/virtualmachine.html:92 +#: templates/virtualization/virtualmachine.html:102 +#: virtualization/filtersets.py:153 virtualization/filtersets.py:268 +#: virtualization/forms/bulk_edit.py:126 virtualization/forms/bulk_import.py:91 +#: virtualization/forms/filtersets.py:95 virtualization/forms/filtersets.py:116 +#: virtualization/forms/filtersets.py:192 +#: virtualization/forms/model_forms.py:81 +#: virtualization/forms/model_forms.py:178 +#: virtualization/tables/virtualmachines.py:55 +msgid "Cluster" +msgstr "" + +#: dcim/forms/bulk_import.py:458 +msgid "Virtualization cluster" +msgstr "" + +#: dcim/forms/bulk_import.py:487 +msgid "Assigned location (if any)" +msgstr "" + +#: dcim/forms/bulk_import.py:494 +msgid "Assigned rack (if any)" +msgstr "" + +#: dcim/forms/bulk_import.py:497 +msgid "Face" +msgstr "" + +#: dcim/forms/bulk_import.py:500 +msgid "Mounted rack face" +msgstr "" + +#: dcim/forms/bulk_import.py:507 +msgid "Parent device (for child devices)" +msgstr "" + +#: dcim/forms/bulk_import.py:510 +msgid "Device bay" +msgstr "" + +#: dcim/forms/bulk_import.py:514 +msgid "Device bay in which this device is installed (for child devices)" +msgstr "" + +#: dcim/forms/bulk_import.py:520 +msgid "Airflow direction" +msgstr "" + +#: dcim/forms/bulk_import.py:581 +msgid "The device in which this module is installed" +msgstr "" + +#: dcim/forms/bulk_import.py:584 dcim/forms/model_forms.py:561 +msgid "Module bay" +msgstr "" + +#: dcim/forms/bulk_import.py:587 +msgid "The module bay in which this module is installed" +msgstr "" + +#: dcim/forms/bulk_import.py:593 +msgid "The type of module" +msgstr "" + +#: dcim/forms/bulk_import.py:601 dcim/forms/model_forms.py:574 +msgid "Replicate components" +msgstr "" + +#: dcim/forms/bulk_import.py:603 +msgid "" +"Automatically populate components associated with this module type (enabled " +"by default)" +msgstr "" + +#: dcim/forms/bulk_import.py:606 dcim/forms/model_forms.py:580 +msgid "Adopt components" +msgstr "" + +#: dcim/forms/bulk_import.py:608 dcim/forms/model_forms.py:583 +msgid "Adopt already existing components" +msgstr "" + +#: dcim/forms/bulk_import.py:648 dcim/forms/bulk_import.py:674 +#: dcim/forms/bulk_import.py:700 +msgid "Port type" +msgstr "" + +#: dcim/forms/bulk_import.py:656 dcim/forms/bulk_import.py:682 +msgid "Port speed in bps" +msgstr "" + +#: dcim/forms/bulk_import.py:720 +msgid "Outlet type" +msgstr "" + +#: dcim/forms/bulk_import.py:727 +msgid "Local power port which feeds this outlet" +msgstr "" + +#: dcim/forms/bulk_import.py:730 +msgid "Feed lag" +msgstr "" + +#: dcim/forms/bulk_import.py:733 +msgid "Electrical phase (for three-phase circuits)" +msgstr "" + +#: dcim/forms/bulk_import.py:774 dcim/forms/model_forms.py:1113 +#: virtualization/forms/bulk_import.py:154 +#: virtualization/forms/model_forms.py:287 +msgid "Parent interface" +msgstr "" + +#: dcim/forms/bulk_import.py:781 dcim/forms/model_forms.py:1121 +#: virtualization/forms/bulk_import.py:161 +#: virtualization/forms/model_forms.py:295 +msgid "Bridged interface" +msgstr "" + +#: dcim/forms/bulk_import.py:784 +msgid "Lag" +msgstr "" + +#: dcim/forms/bulk_import.py:788 +msgid "Parent LAG interface" +msgstr "" + +#: dcim/forms/bulk_import.py:791 +msgid "Vdcs" +msgstr "" + +#: dcim/forms/bulk_import.py:796 +msgid "VDC names separated by commas, encased with double quotes. Example:" +msgstr "" + +#: dcim/forms/bulk_import.py:802 +msgid "Physical medium" +msgstr "" + +#: dcim/forms/bulk_import.py:805 dcim/forms/filtersets.py:1256 +msgid "Duplex" +msgstr "" + +#: dcim/forms/bulk_import.py:810 +msgid "Poe mode" +msgstr "" + +#: dcim/forms/bulk_import.py:816 +msgid "Poe type" +msgstr "" + +#: dcim/forms/bulk_import.py:825 virtualization/forms/bulk_import.py:167 +msgid "IEEE 802.1Q operational mode (for L2 interfaces)" +msgstr "" + +#: dcim/forms/bulk_import.py:832 ipam/forms/bulk_import.py:163 +#: ipam/forms/bulk_import.py:249 ipam/forms/bulk_import.py:285 +#: ipam/forms/filtersets.py:200 ipam/forms/filtersets.py:270 +#: ipam/forms/filtersets.py:325 virtualization/forms/bulk_import.py:174 +msgid "Assigned VRF" +msgstr "" + +#: dcim/forms/bulk_import.py:835 +msgid "Rf role" +msgstr "" + +#: dcim/forms/bulk_import.py:838 +msgid "Wireless role (AP/station)" +msgstr "" + +#: dcim/forms/bulk_import.py:884 dcim/forms/model_forms.py:892 +#: dcim/forms/model_forms.py:1368 dcim/forms/object_import.py:122 +msgid "Rear port" +msgstr "" + +#: dcim/forms/bulk_import.py:887 +msgid "Corresponding rear port" +msgstr "" + +#: dcim/forms/bulk_import.py:892 dcim/forms/bulk_import.py:933 +#: dcim/forms/bulk_import.py:1148 +msgid "Physical medium classification" +msgstr "" + +#: dcim/forms/bulk_import.py:961 dcim/tables/devices.py:824 +msgid "Installed device" +msgstr "" + +#: dcim/forms/bulk_import.py:965 +msgid "Child device installed within this bay" +msgstr "" + +#: dcim/forms/bulk_import.py:967 +msgid "Child device not found." +msgstr "" + +#: dcim/forms/bulk_import.py:1025 +msgid "Parent inventory item" +msgstr "" + +#: dcim/forms/bulk_import.py:1028 +msgid "Component type" +msgstr "" + +#: dcim/forms/bulk_import.py:1032 +msgid "Component Type" +msgstr "" + +#: dcim/forms/bulk_import.py:1035 +msgid "Compnent name" +msgstr "" + +#: dcim/forms/bulk_import.py:1037 +msgid "Component Name" +msgstr "" + +#: dcim/forms/bulk_import.py:1103 +msgid "Side A device" +msgstr "" + +#: dcim/forms/bulk_import.py:1106 dcim/forms/bulk_import.py:1124 +msgid "Device name" +msgstr "" + +#: dcim/forms/bulk_import.py:1109 +msgid "Side A type" +msgstr "" + +#: dcim/forms/bulk_import.py:1112 dcim/forms/bulk_import.py:1130 +msgid "Termination type" +msgstr "" + +#: dcim/forms/bulk_import.py:1115 +msgid "Side A name" +msgstr "" + +#: dcim/forms/bulk_import.py:1116 dcim/forms/bulk_import.py:1134 +msgid "Termination name" +msgstr "" + +#: dcim/forms/bulk_import.py:1121 +msgid "Side B device" +msgstr "" + +#: dcim/forms/bulk_import.py:1127 +msgid "Side B type" +msgstr "" + +#: dcim/forms/bulk_import.py:1133 +msgid "Side B name" +msgstr "" + +#: dcim/forms/bulk_import.py:1142 wireless/forms/bulk_import.py:86 +msgid "Connection status" +msgstr "" + +#: dcim/forms/bulk_import.py:1221 dcim/forms/model_forms.py:688 +#: dcim/tables/devices.py:1007 templates/dcim/device.html:147 +#: templates/dcim/virtualchassis.html:28 templates/dcim/virtualchassis.html:60 +msgid "Master" +msgstr "" + +#: dcim/forms/bulk_import.py:1225 +msgid "Master device" +msgstr "" + +#: dcim/forms/bulk_import.py:1242 +msgid "Name of parent site" +msgstr "" + +#: dcim/forms/bulk_import.py:1276 +msgid "Upstream power panel" +msgstr "" + +#: dcim/forms/bulk_import.py:1306 +msgid "Primary or redundant" +msgstr "" + +#: dcim/forms/bulk_import.py:1311 +msgid "Supply type (AC/DC)" +msgstr "" + +#: dcim/forms/bulk_import.py:1316 +msgid "Single or three-phase" +msgstr "" + +#: dcim/forms/common.py:24 dcim/models/device_components.py:529 +#: templates/dcim/interface.html:58 +#: templates/virtualization/vminterface.html:58 +#: virtualization/forms/bulk_edit.py:222 +msgid "MTU" +msgstr "" + +#: dcim/forms/common.py:65 +#, python-brace-format +msgid "" +"The tagged VLANs ({vlans}) must belong to the same site as the interface's " +"parent device/VM, or they must be global" +msgstr "" + +#: dcim/forms/common.py:110 +msgid "" +"Cannot install module with placeholder values in a module bay with no " +"position defined." +msgstr "" + +#: dcim/forms/common.py:119 +#, python-brace-format +msgid "Cannot adopt {model} {name} as it already belongs to a module" +msgstr "" + +#: dcim/forms/common.py:128 +#, python-brace-format +msgid "A {model} named {name} already exists" +msgstr "" + +#: dcim/forms/connections.py:45 dcim/tables/power.py:66 +#: templates/dcim/inc/cable_termination.html:37 +#: templates/dcim/powerfeed.html:27 templates/dcim/powerpanel.html:19 +#: templates/dcim/trace/powerpanel.html:4 +msgid "Power Panel" +msgstr "" + +#: dcim/forms/connections.py:54 dcim/forms/model_forms.py:669 +#: templates/dcim/powerfeed.html:22 templates/dcim/powerport.html:84 +msgid "Power Feed" +msgstr "" + +#: dcim/forms/connections.py:74 +msgid "Side" +msgstr "" + +#: dcim/forms/filtersets.py:140 +msgid "Parent region" +msgstr "" + +#: dcim/forms/filtersets.py:154 tenancy/forms/bulk_import.py:28 +#: tenancy/forms/bulk_import.py:62 tenancy/forms/filtersets.py:33 +#: tenancy/forms/filtersets.py:62 wireless/forms/bulk_import.py:25 +#: wireless/forms/filtersets.py:24 +msgid "Parent group" +msgstr "" + +#: dcim/forms/filtersets.py:244 dcim/forms/filtersets.py:328 +msgid "Function" +msgstr "" + +#: dcim/forms/filtersets.py:415 dcim/forms/model_forms.py:308 +#: templates/inc/panels/image_attachments.html:5 +msgid "Images" +msgstr "" + +#: dcim/forms/filtersets.py:416 dcim/forms/filtersets.py:540 +#: dcim/forms/filtersets.py:649 +msgid "Components" +msgstr "" + +#: dcim/forms/filtersets.py:437 +msgid "Subdevice role" +msgstr "" + +#: dcim/forms/filtersets.py:652 extras/forms/model_forms.py:496 +#: templates/extras/configrevision.html:171 users/forms/model_forms.py:63 +msgid "Miscellaneous" +msgstr "" + +#: dcim/forms/filtersets.py:710 +msgid "Model" +msgstr "" + +#: dcim/forms/filtersets.py:761 +msgid "Virtual chassis member" +msgstr "" + +#: dcim/forms/filtersets.py:1115 +msgid "Cabled" +msgstr "" + +#: dcim/forms/filtersets.py:1122 +msgid "Occupied" +msgstr "" + +#: dcim/forms/filtersets.py:1147 dcim/forms/filtersets.py:1169 +#: dcim/forms/filtersets.py:1191 dcim/forms/filtersets.py:1208 +#: dcim/forms/filtersets.py:1228 dcim/tables/devices.py:367 +#: templates/dcim/consoleport.html:59 templates/dcim/consoleserverport.html:59 +#: templates/dcim/frontport.html:74 templates/dcim/interface.html:142 +#: templates/dcim/powerfeed.html:118 templates/dcim/poweroutlet.html:63 +#: templates/dcim/powerport.html:63 templates/dcim/rearport.html:70 +msgid "Connection" +msgstr "" + +#: dcim/forms/filtersets.py:1236 dcim/forms/model_forms.py:1476 +#: templates/dcim/virtualdevicecontext.html:16 +msgid "Virtual Device Context" +msgstr "" + +#: dcim/forms/filtersets.py:1239 extras/forms/bulk_edit.py:294 +#: extras/forms/bulk_import.py:177 extras/forms/filtersets.py:454 +#: extras/forms/model_forms.py:445 extras/tables/tables.py:482 +#: templates/extras/journalentry.html:33 +msgid "Kind" +msgstr "" + +#: dcim/forms/filtersets.py:1268 +msgid "Mgmt only" +msgstr "" + +#: dcim/forms/filtersets.py:1280 dcim/forms/model_forms.py:1179 +#: dcim/models/device_components.py:627 templates/dcim/interface.html:130 +msgid "WWN" +msgstr "" + +#: dcim/forms/filtersets.py:1300 +msgid "Wireless channel" +msgstr "" + +#: dcim/forms/filtersets.py:1304 +msgid "Channel frequency (MHz)" +msgstr "" + +#: dcim/forms/filtersets.py:1308 +msgid "Channel width (MHz)" +msgstr "" + +#: dcim/forms/filtersets.py:1312 templates/dcim/interface.html:86 +msgid "Transmit power (dBm)" +msgstr "" + +#: dcim/forms/filtersets.py:1335 dcim/forms/filtersets.py:1357 +#: dcim/tables/devices.py:344 templates/dcim/cable.html:12 +#: templates/dcim/cable_edit.html:46 templates/dcim/cable_trace.html:43 +#: templates/dcim/frontport.html:84 +#: templates/dcim/inc/connection_endpoints.html:4 +#: templates/dcim/rearport.html:80 templates/dcim/trace/cable.html:7 +msgid "Cable" +msgstr "" + +#: dcim/forms/filtersets.py:1425 dcim/tables/devices.py:934 +msgid "Discovered" +msgstr "" + +#: dcim/forms/formsets.py:20 +#, python-brace-format +msgid "A virtual chassis member already exists in position {vc_position}." +msgstr "" + +#: dcim/forms/model_forms.py:101 dcim/tables/devices.py:183 +#: templates/dcim/sitegroup.html:26 +msgid "Site Group" +msgstr "" + +#: dcim/forms/model_forms.py:142 +msgid "Contact Info" +msgstr "" + +#: dcim/forms/model_forms.py:197 templates/dcim/rackrole.html:20 +msgid "Rack Role" +msgstr "" + +#: dcim/forms/model_forms.py:248 +msgid "" +"Comma-separated list of numeric unit IDs. A range may be specified using a " +"hyphen." +msgstr "" + +#: dcim/forms/model_forms.py:259 dcim/tables/racks.py:133 +msgid "Reservation" +msgstr "" + +#: dcim/forms/model_forms.py:297 dcim/forms/model_forms.py:380 +#: utilities/forms/fields/fields.py:47 +msgid "Slug" +msgstr "" + +#: dcim/forms/model_forms.py:304 templates/dcim/devicetype.html:12 +msgid "Chassis" +msgstr "" + +#: dcim/forms/model_forms.py:356 templates/dcim/devicerole.html:24 +msgid "Device Role" +msgstr "" + +#: dcim/forms/model_forms.py:424 dcim/models/devices.py:632 +msgid "The lowest-numbered unit occupied by the device" +msgstr "" + +#: dcim/forms/model_forms.py:468 +msgid "The position in the virtual chassis this device is identified by" +msgstr "" + +#: dcim/forms/model_forms.py:472 templates/dcim/device.html:148 +#: templates/dcim/virtualchassis.html:61 +#: templates/dcim/virtualchassis_edit.html:57 +#: templates/ipam/inc/panels/fhrp_groups.html:13 tenancy/forms/bulk_edit.py:146 +#: tenancy/forms/filtersets.py:111 +msgid "Priority" +msgstr "" + +#: dcim/forms/model_forms.py:473 +msgid "The priority of the device in the virtual chassis" +msgstr "" + +#: dcim/forms/model_forms.py:577 +msgid "Automatically populate components associated with this module type" +msgstr "" + +#: dcim/forms/model_forms.py:622 +msgid "Maximum length is 32767 (any unit)" +msgstr "" + +#: dcim/forms/model_forms.py:670 +msgid "Characteristics" +msgstr "" + +#: dcim/forms/model_forms.py:1129 +msgid "LAG interface" +msgstr "" + +#: dcim/forms/model_forms.py:1183 dcim/forms/model_forms.py:1344 +#: dcim/tables/connections.py:65 ipam/forms/bulk_import.py:320 +#: ipam/forms/bulk_import.py:557 ipam/forms/model_forms.py:272 +#: ipam/forms/model_forms.py:281 ipam/forms/model_forms.py:807 +#: ipam/forms/model_forms.py:816 ipam/tables/fhrp.py:64 ipam/tables/ip.py:368 +#: ipam/tables/vlans.py:165 templates/circuits/inc/circuit_termination.html:78 +#: templates/dcim/frontport.html:113 templates/dcim/interface.html:27 +#: templates/dcim/interface.html:186 templates/dcim/interface.html:318 +#: templates/dcim/inventoryitem_edit.html:54 templates/dcim/rearport.html:109 +#: templates/ipam/fhrpgroupassignment_edit.html:11 +#: templates/virtualization/vminterface.html:19 +#: templates/wireless/inc/wirelesslink_interface.html:10 +#: templates/wireless/wirelesslink.html:10 +#: templates/wireless/wirelesslink.html:49 +#: virtualization/forms/model_forms.py:330 wireless/forms/model_forms.py:112 +#: wireless/forms/model_forms.py:152 +msgid "Interface" +msgstr "" + +#: dcim/forms/model_forms.py:1277 +msgid "Child Device" +msgstr "" + +#: dcim/forms/model_forms.py:1278 +msgid "" +"Child devices must first be created and assigned to the site and rack of the " +"parent device." +msgstr "" + +#: dcim/forms/model_forms.py:1320 +msgid "Console port" +msgstr "" + +#: dcim/forms/model_forms.py:1328 +msgid "Console server port" +msgstr "" + +#: dcim/forms/model_forms.py:1336 +msgid "Front port" +msgstr "" + +#: dcim/forms/model_forms.py:1352 +msgid "Power outlet" +msgstr "" + +#: dcim/forms/model_forms.py:1372 templates/dcim/inventoryitem.html:17 +#: templates/dcim/inventoryitem_edit.html:10 +msgid "Inventory Item" +msgstr "" + +#: dcim/forms/model_forms.py:1424 +msgid "An InventoryItem can only be assigned to a single component." +msgstr "" + +#: dcim/forms/model_forms.py:1438 templates/dcim/inventoryitemrole.html:15 +msgid "Inventory Item Role" +msgstr "" + +#: dcim/forms/model_forms.py:1458 templates/dcim/device.html:212 +#: templates/dcim/virtualdevicecontext.html:33 +#: templates/virtualization/virtualmachine.html:51 +msgid "Primary IPv4" +msgstr "" + +#: dcim/forms/model_forms.py:1467 templates/dcim/device.html:228 +#: templates/dcim/virtualdevicecontext.html:44 +#: templates/virtualization/virtualmachine.html:67 +msgid "Primary IPv6" +msgstr "" + +#: dcim/forms/object_create.py:47 dcim/forms/object_create.py:181 +#: dcim/forms/object_create.py:321 +msgid "" +"Alphanumeric ranges are supported. (Must match the number of objects being " +"created.)" +msgstr "" + +#: dcim/forms/object_create.py:67 +#, python-brace-format +msgid "" +"The provided pattern specifies {value_count} values, but {pattern_count} are " +"expected." +msgstr "" + +#: dcim/forms/object_create.py:109 dcim/forms/object_create.py:253 +#: dcim/tables/devices.py:281 +msgid "Rear ports" +msgstr "" + +#: dcim/forms/object_create.py:110 dcim/forms/object_create.py:254 +msgid "Select one rear port assignment for each front port being created." +msgstr "" + +#: dcim/forms/object_create.py:233 +#, python-brace-format +msgid "" +"The string {module} will be replaced with the position of the " +"assigned module, if any." +msgstr "" + +#: dcim/forms/object_create.py:375 dcim/tables/devices.py:1013 +#: ipam/tables/fhrp.py:31 templates/dcim/virtualchassis.html:54 +#: templates/dcim/virtualchassis_edit.html:48 templates/ipam/fhrpgroup.html:39 +msgid "Members" +msgstr "" + +#: dcim/forms/object_create.py:384 +msgid "Initial position" +msgstr "" + +#: dcim/forms/object_create.py:387 +msgid "" +"Position of the first member device. Increases by one for each additional " +"member." +msgstr "" + +#: dcim/forms/object_create.py:401 +msgid "A position must be specified for the first VC member." +msgstr "" + +#: dcim/models/cables.py:63 dcim/models/device_component_templates.py:56 +#: dcim/models/device_components.py:64 extras/models/customfields.py:102 +msgid "label" +msgstr "" + +#: dcim/models/cables.py:72 +msgid "length" +msgstr "" + +#: dcim/models/cables.py:79 +msgid "length unit" +msgstr "" + +#: dcim/models/cables.py:94 +msgid "cable" +msgstr "" + +#: dcim/models/cables.py:95 +msgid "cables" +msgstr "" + +#: dcim/models/cables.py:247 ipam/models/asns.py:37 +msgid "end" +msgstr "" + +#: dcim/models/cables.py:297 +msgid "cable termination" +msgstr "" + +#: dcim/models/cables.py:298 +msgid "cable terminations" +msgstr "" + +#: dcim/models/cables.py:421 extras/models/configs.py:50 +msgid "is active" +msgstr "" + +#: dcim/models/cables.py:425 +msgid "is complete" +msgstr "" + +#: dcim/models/cables.py:429 +msgid "is split" +msgstr "" + +#: dcim/models/cables.py:435 +msgid "cable path" +msgstr "" + +#: dcim/models/cables.py:436 +msgid "cable paths" +msgstr "" + +#: dcim/models/device_component_templates.py:47 +#, python-brace-format +msgid "" +"{module} is accepted as a substitution for the module bay position when " +"attached to a module type." +msgstr "" + +#: dcim/models/device_component_templates.py:59 +#: dcim/models/device_components.py:67 +msgid "Physical label" +msgstr "" + +#: dcim/models/device_component_templates.py:104 +msgid "Component templates cannot be moved to a different device type." +msgstr "" + +#: dcim/models/device_component_templates.py:155 +msgid "" +"A component template cannot be associated with both a device type and a " +"module type." +msgstr "" + +#: dcim/models/device_component_templates.py:159 +msgid "" +"A component template must be associated with either a device type or a " +"module type." +msgstr "" + +#: dcim/models/device_component_templates.py:187 +msgid "console port template" +msgstr "" + +#: dcim/models/device_component_templates.py:188 +msgid "console port templates" +msgstr "" + +#: dcim/models/device_component_templates.py:221 +msgid "console server port template" +msgstr "" + +#: dcim/models/device_component_templates.py:222 +msgid "console server port templates" +msgstr "" + +#: dcim/models/device_component_templates.py:253 +#: dcim/models/device_components.py:354 +msgid "maximum draw" +msgstr "" + +#: dcim/models/device_component_templates.py:260 +#: dcim/models/device_components.py:361 +msgid "allocated draw" +msgstr "" + +#: dcim/models/device_component_templates.py:270 +msgid "power port template" +msgstr "" + +#: dcim/models/device_component_templates.py:271 +msgid "power port templates" +msgstr "" + +#: dcim/models/device_component_templates.py:290 +#: dcim/models/device_components.py:384 +#, python-brace-format +msgid "Allocated draw cannot exceed the maximum draw ({maximum_draw}W)." +msgstr "" + +#: dcim/models/device_component_templates.py:322 +#: dcim/models/device_components.py:479 +msgid "feed leg" +msgstr "" + +#: dcim/models/device_component_templates.py:326 +#: dcim/models/device_components.py:483 +msgid "Phase (for three-phase feeds)" +msgstr "" + +#: dcim/models/device_component_templates.py:332 +msgid "power outlet template" +msgstr "" + +#: dcim/models/device_component_templates.py:333 +msgid "power outlet templates" +msgstr "" + +#: dcim/models/device_component_templates.py:342 +#, python-brace-format +msgid "Parent power port ({power_port}) must belong to the same device type" +msgstr "" + +#: dcim/models/device_component_templates.py:346 +#, python-brace-format +msgid "Parent power port ({power_port}) must belong to the same module type" +msgstr "" + +#: dcim/models/device_component_templates.py:398 +#: dcim/models/device_components.py:609 +msgid "management only" +msgstr "" + +#: dcim/models/device_component_templates.py:406 +#: dcim/models/device_components.py:552 +msgid "bridge interface" +msgstr "" + +#: dcim/models/device_component_templates.py:424 +#: dcim/models/device_components.py:634 +msgid "wireless role" +msgstr "" + +#: dcim/models/device_component_templates.py:430 +msgid "interface template" +msgstr "" + +#: dcim/models/device_component_templates.py:431 +msgid "interface templates" +msgstr "" + +#: dcim/models/device_component_templates.py:438 +#: dcim/models/device_components.py:796 +#: virtualization/models/virtualmachines.py:340 +msgid "An interface cannot be bridged to itself." +msgstr "" + +#: dcim/models/device_component_templates.py:441 +#, python-brace-format +msgid "Bridge interface ({bridge}) must belong to the same device type" +msgstr "" + +#: dcim/models/device_component_templates.py:445 +#, python-brace-format +msgid "Bridge interface ({bridge}) must belong to the same module type" +msgstr "" + +#: dcim/models/device_component_templates.py:501 +#: dcim/models/device_components.py:976 +msgid "rear port position" +msgstr "" + +#: dcim/models/device_component_templates.py:526 +msgid "front port template" +msgstr "" + +#: dcim/models/device_component_templates.py:527 +msgid "front port templates" +msgstr "" + +#: dcim/models/device_component_templates.py:537 +#, python-brace-format +msgid "Rear port ({name}) must belong to the same device type" +msgstr "" + +#: dcim/models/device_component_templates.py:543 +#, python-brace-format +msgid "" +"Invalid rear port position ({position}); rear port {name} has only {count} " +"positions" +msgstr "" + +#: dcim/models/device_component_templates.py:596 +#: dcim/models/device_components.py:1045 +msgid "positions" +msgstr "" + +#: dcim/models/device_component_templates.py:607 +msgid "rear port template" +msgstr "" + +#: dcim/models/device_component_templates.py:608 +msgid "rear port templates" +msgstr "" + +#: dcim/models/device_component_templates.py:637 +#: dcim/models/device_components.py:1086 +msgid "position" +msgstr "" + +#: dcim/models/device_component_templates.py:640 +#: dcim/models/device_components.py:1089 +msgid "Identifier to reference when renaming installed components" +msgstr "" + +#: dcim/models/device_component_templates.py:646 +msgid "module bay template" +msgstr "" + +#: dcim/models/device_component_templates.py:647 +msgid "module bay templates" +msgstr "" + +#: dcim/models/device_component_templates.py:674 +msgid "device bay template" +msgstr "" + +#: dcim/models/device_component_templates.py:675 +msgid "device bay templates" +msgstr "" + +#: dcim/models/device_component_templates.py:688 +#, python-brace-format +msgid "" +"Subdevice role of device type ({device_type}) must be set to \"parent\" to " +"allow device bays." +msgstr "" + +#: dcim/models/device_component_templates.py:743 +#: dcim/models/device_components.py:1215 +msgid "part ID" +msgstr "" + +#: dcim/models/device_component_templates.py:745 +#: dcim/models/device_components.py:1217 +msgid "Manufacturer-assigned part identifier" +msgstr "" + +#: dcim/models/device_component_templates.py:759 +msgid "inventory item template" +msgstr "" + +#: dcim/models/device_component_templates.py:760 +msgid "inventory item templates" +msgstr "" + +#: dcim/models/device_components.py:107 +msgid "Components cannot be moved to a different device." +msgstr "" + +#: dcim/models/device_components.py:146 +msgid "cable end" +msgstr "" + +#: dcim/models/device_components.py:152 +msgid "mark connected" +msgstr "" + +#: dcim/models/device_components.py:154 +msgid "Treat as if a cable is connected" +msgstr "" + +#: dcim/models/device_components.py:172 +msgid "Must specify cable end (A or B) when attaching a cable." +msgstr "" + +#: dcim/models/device_components.py:176 +msgid "Cable end must not be set without a cable." +msgstr "" + +#: dcim/models/device_components.py:180 +msgid "Cannot mark as connected with a cable attached." +msgstr "" + +#: dcim/models/device_components.py:204 +#, python-brace-format +msgid "{class_name} models must declare a parent_object property" +msgstr "" + +#: dcim/models/device_components.py:289 dcim/models/device_components.py:318 +#: dcim/models/device_components.py:351 dcim/models/device_components.py:469 +msgid "Physical port type" +msgstr "" + +#: dcim/models/device_components.py:292 dcim/models/device_components.py:321 +msgid "speed" +msgstr "" + +#: dcim/models/device_components.py:296 dcim/models/device_components.py:325 +msgid "Port speed in bits per second" +msgstr "" + +#: dcim/models/device_components.py:302 +msgid "console port" +msgstr "" + +#: dcim/models/device_components.py:303 +msgid "console ports" +msgstr "" + +#: dcim/models/device_components.py:331 +msgid "console server port" +msgstr "" + +#: dcim/models/device_components.py:332 +msgid "console server ports" +msgstr "" + +#: dcim/models/device_components.py:371 +msgid "power port" +msgstr "" + +#: dcim/models/device_components.py:372 +msgid "power ports" +msgstr "" + +#: dcim/models/device_components.py:489 +msgid "power outlet" +msgstr "" + +#: dcim/models/device_components.py:490 +msgid "power outlets" +msgstr "" + +#: dcim/models/device_components.py:501 +#, python-brace-format +msgid "Parent power port ({power_port}) must belong to the same device" +msgstr "" + +#: dcim/models/device_components.py:532 +msgid "mode" +msgstr "" + +#: dcim/models/device_components.py:536 +msgid "IEEE 802.1Q tagging strategy" +msgstr "" + +#: dcim/models/device_components.py:544 +msgid "parent interface" +msgstr "" + +#: dcim/models/device_components.py:600 +msgid "parent LAG" +msgstr "" + +#: dcim/models/device_components.py:610 +msgid "This interface is used only for out-of-band management" +msgstr "" + +#: dcim/models/device_components.py:615 +msgid "speed (Kbps)" +msgstr "" + +#: dcim/models/device_components.py:618 +msgid "duplex" +msgstr "" + +#: dcim/models/device_components.py:628 +msgid "64-bit World Wide Name" +msgstr "" + +#: dcim/models/device_components.py:640 +msgid "wireless channel" +msgstr "" + +#: dcim/models/device_components.py:647 +msgid "channel frequency (MHz)" +msgstr "" + +#: dcim/models/device_components.py:648 dcim/models/device_components.py:656 +msgid "Populated by selected channel (if set)" +msgstr "" + +#: dcim/models/device_components.py:662 +msgid "transmit power (dBm)" +msgstr "" + +#: dcim/models/device_components.py:687 wireless/models.py:116 +msgid "wireless LANs" +msgstr "" + +#: dcim/models/device_components.py:695 +#: virtualization/models/virtualmachines.py:266 +msgid "untagged VLAN" +msgstr "" + +#: dcim/models/device_components.py:701 +#: virtualization/models/virtualmachines.py:272 +msgid "tagged VLANs" +msgstr "" + +#: dcim/models/device_components.py:737 +#: virtualization/models/virtualmachines.py:309 +msgid "interface" +msgstr "" + +#: dcim/models/device_components.py:738 +#: virtualization/models/virtualmachines.py:310 +msgid "interfaces" +msgstr "" + +#: dcim/models/device_components.py:749 +#, python-brace-format +msgid "{display_type} interfaces cannot have a cable attached." +msgstr "" + +#: dcim/models/device_components.py:757 +#, python-brace-format +msgid "{display_type} interfaces cannot be marked as connected." +msgstr "" + +#: dcim/models/device_components.py:766 +#: virtualization/models/virtualmachines.py:325 +msgid "An interface cannot be its own parent." +msgstr "" + +#: dcim/models/device_components.py:770 +msgid "Only virtual interfaces may be assigned to a parent interface." +msgstr "" + +#: dcim/models/device_components.py:777 +#, python-brace-format +msgid "" +"The selected parent interface ({interface}) belongs to a different device " +"({device})" +msgstr "" + +#: dcim/models/device_components.py:783 +#, python-brace-format +msgid "" +"The selected parent interface ({interface}) belongs to {device}, which is " +"not part of virtual chassis {virtual_chassis}." +msgstr "" + +#: dcim/models/device_components.py:803 +#, python-brace-format +msgid "" +"The selected bridge interface ({bridge}) belongs to a different device " +"({device})." +msgstr "" + +#: dcim/models/device_components.py:809 +#, python-brace-format +msgid "" +"The selected bridge interface ({interface}) belongs to {device}, which is " +"not part of virtual chassis {virtual_chassis}." +msgstr "" + +#: dcim/models/device_components.py:820 +msgid "Virtual interfaces cannot have a parent LAG interface." +msgstr "" + +#: dcim/models/device_components.py:824 +msgid "A LAG interface cannot be its own parent." +msgstr "" + +#: dcim/models/device_components.py:831 +#, python-brace-format +msgid "" +"The selected LAG interface ({lag}) belongs to a different device ({device})." +msgstr "" + +#: dcim/models/device_components.py:837 +#, python-brace-format +msgid "" +"The selected LAG interface ({lag}) belongs to {device}, which is not part of " +"virtual chassis {virtual_chassis}." +msgstr "" + +#: dcim/models/device_components.py:848 +msgid "Virtual interfaces cannot have a PoE mode." +msgstr "" + +#: dcim/models/device_components.py:852 +msgid "Virtual interfaces cannot have a PoE type." +msgstr "" + +#: dcim/models/device_components.py:858 +msgid "Must specify PoE mode when designating a PoE type." +msgstr "" + +#: dcim/models/device_components.py:865 +msgid "Wireless role may be set only on wireless interfaces." +msgstr "" + +#: dcim/models/device_components.py:867 +msgid "Channel may be set only on wireless interfaces." +msgstr "" + +#: dcim/models/device_components.py:873 +msgid "Channel frequency may be set only on wireless interfaces." +msgstr "" + +#: dcim/models/device_components.py:877 +msgid "Cannot specify custom frequency with channel selected." +msgstr "" + +#: dcim/models/device_components.py:883 +msgid "Channel width may be set only on wireless interfaces." +msgstr "" + +#: dcim/models/device_components.py:885 +msgid "Cannot specify custom width with channel selected." +msgstr "" + +#: dcim/models/device_components.py:893 +#, python-brace-format +msgid "" +"The untagged VLAN ({untagged_vlan}) must belong to the same site as the " +"interface's parent device, or it must be global." +msgstr "" + +#: dcim/models/device_components.py:982 +msgid "Mapped position on corresponding rear port" +msgstr "" + +#: dcim/models/device_components.py:998 +msgid "front port" +msgstr "" + +#: dcim/models/device_components.py:999 +msgid "front ports" +msgstr "" + +#: dcim/models/device_components.py:1013 +#, python-brace-format +msgid "Rear port ({rear_port}) must belong to the same device" +msgstr "" + +#: dcim/models/device_components.py:1021 +#, python-brace-format +msgid "" +"Invalid rear port position ({rear_port_position}): Rear port {name} has only " +"{positions} positions." +msgstr "" + +#: dcim/models/device_components.py:1051 +msgid "Number of front ports which may be mapped" +msgstr "" + +#: dcim/models/device_components.py:1056 +msgid "rear port" +msgstr "" + +#: dcim/models/device_components.py:1057 +msgid "rear ports" +msgstr "" + +#: dcim/models/device_components.py:1071 +#, python-brace-format +msgid "" +"The number of positions cannot be less than the number of mapped front ports " +"({frontport_count})" +msgstr "" + +#: dcim/models/device_components.py:1095 +msgid "module bay" +msgstr "" + +#: dcim/models/device_components.py:1096 +msgid "module bays" +msgstr "" + +#: dcim/models/device_components.py:1109 +msgid "parent_bay" +msgstr "" + +#: dcim/models/device_components.py:1117 +msgid "device bay" +msgstr "" + +#: dcim/models/device_components.py:1118 +msgid "device bays" +msgstr "" + +#: dcim/models/device_components.py:1128 +#, python-brace-format +msgid "This type of device ({device_type}) does not support device bays." +msgstr "" + +#: dcim/models/device_components.py:1134 +msgid "Cannot install a device into itself." +msgstr "" + +#: dcim/models/device_components.py:1142 +#, python-brace-format +msgid "" +"Cannot install the specified device; device is already installed in {bay}." +msgstr "" + +#: dcim/models/device_components.py:1163 +msgid "inventory item role" +msgstr "" + +#: dcim/models/device_components.py:1164 +msgid "inventory item roles" +msgstr "" + +#: dcim/models/device_components.py:1221 dcim/models/devices.py:595 +#: dcim/models/devices.py:1168 dcim/models/racks.py:113 +msgid "serial number" +msgstr "" + +#: dcim/models/device_components.py:1229 dcim/models/devices.py:603 +#: dcim/models/devices.py:1175 dcim/models/racks.py:120 +msgid "asset tag" +msgstr "" + +#: dcim/models/device_components.py:1230 +msgid "A unique tag used to identify this item" +msgstr "" + +#: dcim/models/device_components.py:1233 +msgid "discovered" +msgstr "" + +#: dcim/models/device_components.py:1235 +msgid "This item was automatically discovered" +msgstr "" + +#: dcim/models/device_components.py:1250 +msgid "inventory item" +msgstr "" + +#: dcim/models/device_components.py:1251 +msgid "inventory items" +msgstr "" + +#: dcim/models/device_components.py:1262 +msgid "Cannot assign self as parent." +msgstr "" + +#: dcim/models/device_components.py:1270 +msgid "Parent inventory item does not belong to the same device." +msgstr "" + +#: dcim/models/device_components.py:1276 +msgid "Cannot move an inventory item with dependent children" +msgstr "" + +#: dcim/models/device_components.py:1284 +msgid "Cannot assign inventory item to component on another device" +msgstr "" + +#: dcim/models/devices.py:54 +msgid "manufacturer" +msgstr "" + +#: dcim/models/devices.py:55 +msgid "manufacturers" +msgstr "" + +#: dcim/models/devices.py:82 dcim/models/devices.py:381 +msgid "model" +msgstr "" + +#: dcim/models/devices.py:95 +msgid "default platform" +msgstr "" + +#: dcim/models/devices.py:98 dcim/models/devices.py:385 +msgid "part number" +msgstr "" + +#: dcim/models/devices.py:101 dcim/models/devices.py:388 +msgid "Discrete part number (optional)" +msgstr "" + +#: dcim/models/devices.py:107 dcim/models/racks.py:137 +msgid "height (U)" +msgstr "" + +#: dcim/models/devices.py:111 +msgid "exclude from utilization" +msgstr "" + +#: dcim/models/devices.py:112 +msgid "Exclude from rack utilization calculations." +msgstr "" + +#: dcim/models/devices.py:116 +msgid "is full depth" +msgstr "" + +#: dcim/models/devices.py:117 +msgid "Device consumes both front and rear rack faces" +msgstr "" + +#: dcim/models/devices.py:123 +msgid "parent/child status" +msgstr "" + +#: dcim/models/devices.py:124 +msgid "" +"Parent devices house child devices in device bays. Leave blank if this " +"device type is neither a parent nor a child." +msgstr "" + +#: dcim/models/devices.py:128 dcim/models/devices.py:647 +msgid "airflow" +msgstr "" + +#: dcim/models/devices.py:204 +msgid "device type" +msgstr "" + +#: dcim/models/devices.py:205 +msgid "device types" +msgstr "" + +#: dcim/models/devices.py:289 +msgid "U height must be in increments of 0.5 rack units." +msgstr "" + +#: dcim/models/devices.py:306 +#, python-brace-format +msgid "" +"Device {device} in rack {rack} does not have sufficient space to accommodate " +"a height of {height}U" +msgstr "" + +#: dcim/models/devices.py:321 +#, python-brace-format +msgid "" +"Unable to set 0U height: Found {racked_instance_count} " +"instances already mounted within racks." +msgstr "" + +#: dcim/models/devices.py:330 +msgid "" +"Must delete all device bay templates associated with this device before " +"declassifying it as a parent device." +msgstr "" + +#: dcim/models/devices.py:336 +msgid "Child device types must be 0U." +msgstr "" + +#: dcim/models/devices.py:404 +msgid "module type" +msgstr "" + +#: dcim/models/devices.py:405 +msgid "module types" +msgstr "" + +#: dcim/models/devices.py:473 +msgid "Virtual machines may be assigned to this role" +msgstr "" + +#: dcim/models/devices.py:485 +msgid "device role" +msgstr "" + +#: dcim/models/devices.py:486 +msgid "device roles" +msgstr "" + +#: dcim/models/devices.py:503 +msgid "Optionally limit this platform to devices of a certain manufacturer" +msgstr "" + +#: dcim/models/devices.py:515 +msgid "platform" +msgstr "" + +#: dcim/models/devices.py:516 +msgid "platforms" +msgstr "" + +#: dcim/models/devices.py:564 +msgid "The function this device serves" +msgstr "" + +#: dcim/models/devices.py:596 +msgid "Chassis serial number, assigned by the manufacturer" +msgstr "" + +#: dcim/models/devices.py:604 dcim/models/devices.py:1176 +msgid "A unique tag used to identify this device" +msgstr "" + +#: dcim/models/devices.py:631 +msgid "position (U)" +msgstr "" + +#: dcim/models/devices.py:638 +msgid "rack face" +msgstr "" + +#: dcim/models/devices.py:658 dcim/models/devices.py:1385 +#: virtualization/models/virtualmachines.py:97 +msgid "primary IPv4" +msgstr "" + +#: dcim/models/devices.py:666 dcim/models/devices.py:1393 +#: virtualization/models/virtualmachines.py:105 +msgid "primary IPv6" +msgstr "" + +#: dcim/models/devices.py:674 +msgid "out-of-band IP" +msgstr "" + +#: dcim/models/devices.py:691 +msgid "VC position" +msgstr "" + +#: dcim/models/devices.py:695 +msgid "Virtual chassis position" +msgstr "" + +#: dcim/models/devices.py:698 +msgid "VC priority" +msgstr "" + +#: dcim/models/devices.py:702 +msgid "Virtual chassis master election priority" +msgstr "" + +#: dcim/models/devices.py:705 dcim/models/sites.py:207 +msgid "latitude" +msgstr "" + +#: dcim/models/devices.py:710 dcim/models/devices.py:718 +#: dcim/models/sites.py:212 dcim/models/sites.py:220 +msgid "GPS coordinate in decimal format (xx.yyyyyy)" +msgstr "" + +#: dcim/models/devices.py:713 dcim/models/sites.py:215 +msgid "longitude" +msgstr "" + +#: dcim/models/devices.py:786 +msgid "Device name must be unique per site." +msgstr "" + +#: dcim/models/devices.py:797 ipam/models/services.py:75 +msgid "device" +msgstr "" + +#: dcim/models/devices.py:798 +msgid "devices" +msgstr "" + +#: dcim/models/devices.py:838 +#, python-brace-format +msgid "Rack {rack} does not belong to site {site}." +msgstr "" + +#: dcim/models/devices.py:843 +#, python-brace-format +msgid "Location {location} does not belong to site {site}." +msgstr "" + +#: dcim/models/devices.py:849 +#, python-brace-format +msgid "Rack {rack} does not belong to location {location}." +msgstr "" + +#: dcim/models/devices.py:856 +msgid "Cannot select a rack face without assigning a rack." +msgstr "" + +#: dcim/models/devices.py:860 +msgid "Cannot select a rack position without assigning a rack." +msgstr "" + +#: dcim/models/devices.py:866 +msgid "Position must be in increments of 0.5 rack units." +msgstr "" + +#: dcim/models/devices.py:870 +msgid "Must specify rack face when defining rack position." +msgstr "" + +#: dcim/models/devices.py:878 +#, python-brace-format +msgid "A U0 device type ({device_type}) cannot be assigned to a rack position." +msgstr "" + +#: dcim/models/devices.py:889 +msgid "" +"Child device types cannot be assigned to a rack face. This is an attribute " +"of the parent device." +msgstr "" + +#: dcim/models/devices.py:896 +msgid "" +"Child device types cannot be assigned to a rack position. This is an " +"attribute of the parent device." +msgstr "" + +#: dcim/models/devices.py:910 +#, python-brace-format +msgid "" +"U{position} is already occupied or does not have sufficient space to " +"accommodate this device type: {device_type} ({u_height}U)" +msgstr "" + +#: dcim/models/devices.py:925 +#, python-brace-format +msgid "{ip} is not an IPv4 address." +msgstr "" + +#: dcim/models/devices.py:934 dcim/models/devices.py:949 +#, python-brace-format +msgid "The specified IP address ({ip}) is not assigned to this device." +msgstr "" + +#: dcim/models/devices.py:940 +#, python-brace-format +msgid "{ip} is not an IPv6 address." +msgstr "" + +#: dcim/models/devices.py:967 +#, python-brace-format +msgid "" +"The assigned platform is limited to {platform_manufacturer} device types, " +"but this device's type belongs to {devicetype_manufacturer}." +msgstr "" + +#: dcim/models/devices.py:978 +#, python-brace-format +msgid "The assigned cluster belongs to a different site ({site})" +msgstr "" + +#: dcim/models/devices.py:986 +msgid "A device assigned to a virtual chassis must have its position defined." +msgstr "" + +#: dcim/models/devices.py:1183 +msgid "module" +msgstr "" + +#: dcim/models/devices.py:1184 +msgid "modules" +msgstr "" + +#: dcim/models/devices.py:1200 +#, python-brace-format +msgid "" +"Module must be installed within a module bay belonging to the assigned " +"device ({device})." +msgstr "" + +#: dcim/models/devices.py:1304 +msgid "domain" +msgstr "" + +#: dcim/models/devices.py:1317 dcim/models/devices.py:1318 +msgid "virtual chassis" +msgstr "" + +#: dcim/models/devices.py:1333 +#, python-brace-format +msgid "The selected master ({master}) is not assigned to this virtual chassis." +msgstr "" + +#: dcim/models/devices.py:1349 +#, python-brace-format +msgid "" +"Unable to delete virtual chassis {self}. There are member interfaces which " +"form a cross-chassis LAG interfaces." +msgstr "" + +#: dcim/models/devices.py:1374 ipam/models/l2vpn.py:37 +msgid "identifier" +msgstr "" + +#: dcim/models/devices.py:1375 +msgid "Numeric identifier unique to the parent device" +msgstr "" + +#: dcim/models/devices.py:1403 extras/models/models.py:629 +#: netbox/models/__init__.py:114 +msgid "comments" +msgstr "" + +#: dcim/models/devices.py:1419 +msgid "virtual device context" +msgstr "" + +#: dcim/models/devices.py:1420 +msgid "virtual device contexts" +msgstr "" + +#: dcim/models/devices.py:1452 +#, python-brace-format +msgid "{ip} is not an IPv{family} address." +msgstr "" + +#: dcim/models/devices.py:1458 +msgid "Primary IP address must belong to an interface on the assigned device." +msgstr "" + +#: dcim/models/mixins.py:15 extras/models/configs.py:41 +#: extras/models/models.py:260 extras/models/models.py:469 +#: extras/models/search.py:48 ipam/models/ip.py:193 +msgid "weight" +msgstr "" + +#: dcim/models/mixins.py:22 +msgid "weight unit" +msgstr "" + +#: dcim/models/mixins.py:51 +msgid "Must specify a unit when setting a weight" +msgstr "" + +#: dcim/models/power.py:55 +msgid "power panel" +msgstr "" + +#: dcim/models/power.py:56 +msgid "power panels" +msgstr "" + +#: dcim/models/power.py:70 +#, python-brace-format +msgid "" +"Location {location} ({location_site}) is in a different site than {site}" +msgstr "" + +#: dcim/models/power.py:107 +msgid "supply" +msgstr "" + +#: dcim/models/power.py:113 +msgid "phase" +msgstr "" + +#: dcim/models/power.py:119 +msgid "voltage" +msgstr "" + +#: dcim/models/power.py:124 +msgid "amperage" +msgstr "" + +#: dcim/models/power.py:129 +msgid "max utilization" +msgstr "" + +#: dcim/models/power.py:132 +msgid "Maximum permissible draw (percentage)" +msgstr "" + +#: dcim/models/power.py:135 +msgid "available power" +msgstr "" + +#: dcim/models/power.py:163 +msgid "power feed" +msgstr "" + +#: dcim/models/power.py:164 +msgid "power feeds" +msgstr "" + +#: dcim/models/power.py:178 +#, python-brace-format +msgid "" +"Rack {rack} ({site}) and power panel {powerpanel} ({powerpanel_site}) are in " +"different sites" +msgstr "" + +#: dcim/models/power.py:189 +msgid "Voltage cannot be negative for AC supply" +msgstr "" + +#: dcim/models/racks.py:49 +msgid "rack role" +msgstr "" + +#: dcim/models/racks.py:50 +msgid "rack roles" +msgstr "" + +#: dcim/models/racks.py:74 +msgid "facility ID" +msgstr "" + +#: dcim/models/racks.py:75 +msgid "Locally-assigned identifier" +msgstr "" + +#: dcim/models/racks.py:108 ipam/forms/bulk_import.py:203 +#: ipam/forms/bulk_import.py:268 ipam/forms/bulk_import.py:303 +#: ipam/forms/bulk_import.py:470 virtualization/forms/bulk_import.py:111 +msgid "Functional role" +msgstr "" + +#: dcim/models/racks.py:121 +msgid "A unique tag used to identify this rack" +msgstr "" + +#: dcim/models/racks.py:132 +msgid "width" +msgstr "" + +#: dcim/models/racks.py:133 +msgid "Rail-to-rail width" +msgstr "" + +#: dcim/models/racks.py:139 +msgid "Height in rack units" +msgstr "" + +#: dcim/models/racks.py:143 +msgid "starting unit" +msgstr "" + +#: dcim/models/racks.py:144 +msgid "Starting unit for rack" +msgstr "" + +#: dcim/models/racks.py:148 +msgid "descending units" +msgstr "" + +#: dcim/models/racks.py:149 +msgid "Units are numbered top-to-bottom" +msgstr "" + +#: dcim/models/racks.py:152 +msgid "outer width" +msgstr "" + +#: dcim/models/racks.py:155 +msgid "Outer dimension of rack (width)" +msgstr "" + +#: dcim/models/racks.py:158 +msgid "outer depth" +msgstr "" + +#: dcim/models/racks.py:161 +msgid "Outer dimension of rack (depth)" +msgstr "" + +#: dcim/models/racks.py:164 +msgid "outer unit" +msgstr "" + +#: dcim/models/racks.py:170 +msgid "max weight" +msgstr "" + +#: dcim/models/racks.py:173 +msgid "Maximum load capacity for the rack" +msgstr "" + +#: dcim/models/racks.py:181 +msgid "mounting depth" +msgstr "" + +#: dcim/models/racks.py:185 +msgid "" +"Maximum depth of a mounted device, in millimeters. For four-post racks, this " +"is the distance between the front and rear rails." +msgstr "" + +#: dcim/models/racks.py:219 +msgid "rack" +msgstr "" + +#: dcim/models/racks.py:220 +msgid "racks" +msgstr "" + +#: dcim/models/racks.py:235 +#, python-brace-format +msgid "Assigned location must belong to parent site ({site})." +msgstr "" + +#: dcim/models/racks.py:239 +msgid "Must specify a unit when setting an outer width/depth" +msgstr "" + +#: dcim/models/racks.py:243 +msgid "Must specify a unit when setting a maximum weight" +msgstr "" + +#: dcim/models/racks.py:253 +#, python-brace-format +msgid "" +"Rack must be at least {min_height}U tall to house currently installed " +"devices." +msgstr "" + +#: dcim/models/racks.py:260 +#, python-brace-format +msgid "" +"Rack unit numbering must begin at {position} or less to house currently " +"installed devices." +msgstr "" + +#: dcim/models/racks.py:268 +#, python-brace-format +msgid "Location must be from the same site, {site}." +msgstr "" + +#: dcim/models/racks.py:521 +msgid "units" +msgstr "" + +#: dcim/models/racks.py:547 +msgid "rack reservation" +msgstr "" + +#: dcim/models/racks.py:548 +msgid "rack reservations" +msgstr "" + +#: dcim/models/racks.py:565 +#, python-brace-format +msgid "Invalid unit(s) for {height}U rack: {unit_list}" +msgstr "" + +#: dcim/models/racks.py:578 +#, python-brace-format +msgid "The following units have already been reserved: {unit_list}" +msgstr "" + +#: dcim/models/sites.py:49 +msgid "A top-level region with this name already exists." +msgstr "" + +#: dcim/models/sites.py:59 +msgid "A top-level region with this slug already exists." +msgstr "" + +#: dcim/models/sites.py:62 +msgid "region" +msgstr "" + +#: dcim/models/sites.py:63 +msgid "regions" +msgstr "" + +#: dcim/models/sites.py:102 +msgid "A top-level site group with this name already exists." +msgstr "" + +#: dcim/models/sites.py:112 +msgid "A top-level site group with this slug already exists." +msgstr "" + +#: dcim/models/sites.py:115 +msgid "site group" +msgstr "" + +#: dcim/models/sites.py:116 +msgid "site groups" +msgstr "" + +#: dcim/models/sites.py:141 +msgid "Full name of the site" +msgstr "" + +#: dcim/models/sites.py:181 +msgid "facility" +msgstr "" + +#: dcim/models/sites.py:184 +msgid "Local facility ID or description" +msgstr "" + +#: dcim/models/sites.py:195 +msgid "physical address" +msgstr "" + +#: dcim/models/sites.py:198 +msgid "Physical location of the building" +msgstr "" + +#: dcim/models/sites.py:201 +msgid "shipping address" +msgstr "" + +#: dcim/models/sites.py:204 +msgid "If different from the physical address" +msgstr "" + +#: dcim/models/sites.py:238 +msgid "site" +msgstr "" + +#: dcim/models/sites.py:239 +msgid "sites" +msgstr "" + +#: dcim/models/sites.py:303 +msgid "A location with this name already exists within the specified site." +msgstr "" + +#: dcim/models/sites.py:313 +msgid "A location with this slug already exists within the specified site." +msgstr "" + +#: dcim/models/sites.py:316 +msgid "location" +msgstr "" + +#: dcim/models/sites.py:317 +msgid "locations" +msgstr "" + +#: dcim/models/sites.py:331 +#, python-brace-format +msgid "Parent location ({parent}) must belong to the same site ({site})." +msgstr "" + +#: dcim/tables/cables.py:54 +msgid "Termination A" +msgstr "" + +#: dcim/tables/cables.py:59 +msgid "Termination B" +msgstr "" + +#: dcim/tables/cables.py:65 wireless/tables/wirelesslink.py:22 +msgid "Device A" +msgstr "" + +#: dcim/tables/cables.py:71 wireless/tables/wirelesslink.py:31 +msgid "Device B" +msgstr "" + +#: dcim/tables/cables.py:77 +msgid "Location A" +msgstr "" + +#: dcim/tables/cables.py:83 +msgid "Location B" +msgstr "" + +#: dcim/tables/cables.py:89 +msgid "Rack A" +msgstr "" + +#: dcim/tables/cables.py:95 +msgid "Rack B" +msgstr "" + +#: dcim/tables/cables.py:101 +msgid "Site A" +msgstr "" + +#: dcim/tables/cables.py:107 +msgid "Site B" +msgstr "" + +#: dcim/tables/connections.py:27 templates/dcim/consoleport.html:18 +#: templates/dcim/consoleserverport.html:75 templates/dcim/frontport.html:119 +#: templates/dcim/inventoryitem_edit.html:39 +msgid "Console Port" +msgstr "" + +#: dcim/tables/connections.py:31 dcim/tables/connections.py:50 +#: dcim/tables/connections.py:71 +#: templates/dcim/inc/connection_endpoints.html:16 +msgid "Reachable" +msgstr "" + +#: dcim/tables/connections.py:46 dcim/tables/devices.py:518 +#: templates/dcim/inventoryitem_edit.html:64 templates/dcim/poweroutlet.html:47 +#: templates/dcim/powerport.html:18 +msgid "Power Port" +msgstr "" + +#: dcim/tables/devices.py:94 dcim/tables/devices.py:139 dcim/tables/racks.py:81 +#: dcim/tables/sites.py:143 netbox/navigation/menu.py:57 +#: netbox/navigation/menu.py:61 netbox/navigation/menu.py:63 +#: virtualization/forms/model_forms.py:124 virtualization/tables/clusters.py:83 +#: virtualization/views.py:211 +msgid "Devices" +msgstr "" + +#: dcim/tables/devices.py:99 dcim/tables/devices.py:144 +#: virtualization/tables/clusters.py:88 +msgid "VMs" +msgstr "" + +#: dcim/tables/devices.py:133 dcim/tables/devices.py:245 +#: extras/forms/model_forms.py:403 templates/dcim/device.html:131 +#: templates/dcim/device/render_config.html:11 +#: templates/dcim/device/render_config.html:15 +#: templates/dcim/devicerole.html:47 templates/dcim/platform.html:44 +#: templates/extras/configtemplate.html:10 +#: templates/virtualization/virtualmachine.html:47 +#: templates/virtualization/virtualmachine/render_config.html:11 +#: templates/virtualization/virtualmachine/render_config.html:15 +#: virtualization/tables/virtualmachines.py:88 +msgid "Config Template" +msgstr "" + +#: dcim/tables/devices.py:216 dcim/tables/devices.py:1048 +#: ipam/forms/model_forms.py:298 ipam/tables/ip.py:352 ipam/tables/ip.py:418 +#: ipam/tables/ip.py:441 templates/ipam/ipaddress.html:12 +#: templates/ipam/ipaddress_edit.html:14 +#: virtualization/tables/virtualmachines.py:79 +msgid "IP Address" +msgstr "" + +#: dcim/tables/devices.py:220 dcim/tables/devices.py:1052 +#: virtualization/tables/virtualmachines.py:70 +msgid "IPv4 Address" +msgstr "" + +#: dcim/tables/devices.py:224 dcim/tables/devices.py:1056 +#: virtualization/tables/virtualmachines.py:74 +msgid "IPv6 Address" +msgstr "" + +#: dcim/tables/devices.py:239 +msgid "VC Position" +msgstr "" + +#: dcim/tables/devices.py:242 +msgid "VC Priority" +msgstr "" + +#: dcim/tables/devices.py:249 templates/dcim/device_edit.html:38 +#: templates/dcim/devicebay_populate.html:16 +msgid "Parent Device" +msgstr "" + +#: dcim/tables/devices.py:254 +msgid "Position (Device Bay)" +msgstr "" + +#: dcim/tables/devices.py:263 +msgid "Console ports" +msgstr "" + +#: dcim/tables/devices.py:266 +msgid "Console server ports" +msgstr "" + +#: dcim/tables/devices.py:269 +msgid "Power ports" +msgstr "" + +#: dcim/tables/devices.py:272 +msgid "Power outlets" +msgstr "" + +#: dcim/tables/devices.py:275 dcim/tables/devices.py:1061 +#: dcim/tables/devicetypes.py:125 dcim/views.py:1002 dcim/views.py:1241 +#: dcim/views.py:1927 netbox/navigation/menu.py:82 +#: netbox/navigation/menu.py:220 templates/dcim/device/base.html:37 +#: templates/dcim/device_list.html:43 templates/dcim/devicetype/base.html:34 +#: templates/dcim/module.html:34 templates/dcim/moduletype/base.html:34 +#: templates/dcim/virtualdevicecontext.html:64 +#: templates/dcim/virtualdevicecontext.html:85 +#: templates/virtualization/virtualmachine_list.html:14 +#: virtualization/tables/virtualmachines.py:85 virtualization/views.py:368 +#: wireless/tables/wirelesslan.py:55 +msgid "Interfaces" +msgstr "" + +#: dcim/tables/devices.py:278 +msgid "Front ports" +msgstr "" + +#: dcim/tables/devices.py:284 +msgid "Device bays" +msgstr "" + +#: dcim/tables/devices.py:287 +msgid "Module bays" +msgstr "" + +#: dcim/tables/devices.py:290 +msgid "Inventory items" +msgstr "" + +#: dcim/tables/devices.py:329 dcim/tables/modules.py:56 +#: templates/dcim/modulebay.html:17 +msgid "Module Bay" +msgstr "" + +#: dcim/tables/devices.py:350 +msgid "Cable Color" +msgstr "" + +#: dcim/tables/devices.py:356 +msgid "Link Peers" +msgstr "" + +#: dcim/tables/devices.py:359 +msgid "Mark Connected" +msgstr "" + +#: dcim/tables/devices.py:567 ipam/forms/model_forms.py:709 +#: ipam/tables/fhrp.py:28 ipam/views.py:599 ipam/views.py:673 +#: netbox/navigation/menu.py:146 netbox/navigation/menu.py:148 +#: templates/dcim/interface.html:347 templates/ipam/ipaddress_bulk_add.html:15 +#: templates/ipam/service.html:43 templates/virtualization/vminterface.html:84 +msgid "IP Addresses" +msgstr "" + +#: dcim/tables/devices.py:573 netbox/navigation/menu.py:190 +#: templates/ipam/inc/panels/fhrp_groups.html:5 +msgid "FHRP Groups" +msgstr "" + +#: dcim/tables/devices.py:604 dcim/tables/devicetypes.py:224 +#: templates/dcim/interface.html:66 +msgid "Management Only" +msgstr "" + +#: dcim/tables/devices.py:612 +msgid "Wireless link" +msgstr "" + +#: dcim/tables/devices.py:622 +msgid "VDCs" +msgstr "" + +#: dcim/tables/devices.py:706 +#: templates/circuits/inc/circuit_termination.html:80 +#: templates/dcim/consoleport.html:81 templates/dcim/consoleserverport.html:81 +#: templates/dcim/frontport.html:53 templates/dcim/frontport.html:125 +#: templates/dcim/interface.html:192 templates/dcim/inventoryitem_edit.html:69 +#: templates/dcim/rearport.html:18 templates/dcim/rearport.html:115 +msgid "Rear Port" +msgstr "" + +#: dcim/tables/devices.py:871 templates/dcim/modulebay.html:51 +msgid "Installed Module" +msgstr "" + +#: dcim/tables/devices.py:874 +msgid "Module Serial" +msgstr "" + +#: dcim/tables/devices.py:878 +msgid "Module Asset Tag" +msgstr "" + +#: dcim/tables/devices.py:887 +msgid "Module Status" +msgstr "" + +#: dcim/tables/devices.py:929 dcim/tables/devicetypes.py:308 +#: templates/dcim/inventoryitem.html:41 +msgid "Component" +msgstr "" + +#: dcim/tables/devices.py:980 +msgid "Items" +msgstr "" + +#: dcim/tables/devicetypes.py:38 netbox/navigation/menu.py:72 +#: netbox/navigation/menu.py:74 +msgid "Device Types" +msgstr "" + +#: dcim/tables/devicetypes.py:43 netbox/navigation/menu.py:75 +msgid "Module Types" +msgstr "" + +#: dcim/tables/devicetypes.py:48 dcim/tables/devicetypes.py:140 +#: dcim/views.py:1077 dcim/views.py:2020 netbox/navigation/menu.py:91 +#: templates/dcim/device/base.html:52 templates/dcim/device_list.html:71 +#: templates/dcim/devicetype/base.html:49 +#: templates/dcim/inc/panels/inventory_items.html:5 +#: templates/dcim/inventoryitemrole.html:33 +msgid "Inventory Items" +msgstr "" + +#: dcim/tables/devicetypes.py:53 extras/forms/filtersets.py:354 +#: extras/forms/model_forms.py:311 netbox/navigation/menu.py:66 +msgid "Platforms" +msgstr "" + +#: dcim/tables/devicetypes.py:85 templates/dcim/devicetype.html:32 +msgid "Default Platform" +msgstr "" + +#: dcim/tables/devicetypes.py:89 templates/dcim/devicetype.html:48 +msgid "Full Depth" +msgstr "" + +#: dcim/tables/devicetypes.py:98 +msgid "U Height" +msgstr "" + +#: dcim/tables/devicetypes.py:110 dcim/tables/modules.py:26 +msgid "Instances" +msgstr "" + +#: dcim/tables/devicetypes.py:113 dcim/views.py:942 dcim/views.py:1181 +#: dcim/views.py:1867 netbox/navigation/menu.py:85 +#: templates/dcim/device/base.html:25 templates/dcim/device_list.html:15 +#: templates/dcim/devicetype/base.html:22 templates/dcim/module.html:22 +#: templates/dcim/moduletype/base.html:22 +msgid "Console Ports" +msgstr "" + +#: dcim/tables/devicetypes.py:116 dcim/views.py:957 dcim/views.py:1196 +#: dcim/views.py:1882 netbox/navigation/menu.py:86 +#: templates/dcim/device/base.html:28 templates/dcim/device_list.html:22 +#: templates/dcim/devicetype/base.html:25 templates/dcim/module.html:25 +#: templates/dcim/moduletype/base.html:25 +msgid "Console Server Ports" +msgstr "" + +#: dcim/tables/devicetypes.py:119 dcim/views.py:972 dcim/views.py:1211 +#: dcim/views.py:1897 netbox/navigation/menu.py:87 +#: templates/dcim/device/base.html:31 templates/dcim/device_list.html:29 +#: templates/dcim/devicetype/base.html:28 templates/dcim/module.html:28 +#: templates/dcim/moduletype/base.html:28 +msgid "Power Ports" +msgstr "" + +#: dcim/tables/devicetypes.py:122 dcim/views.py:987 dcim/views.py:1226 +#: dcim/views.py:1912 netbox/navigation/menu.py:88 +#: templates/dcim/device/base.html:34 templates/dcim/device_list.html:36 +#: templates/dcim/devicetype/base.html:31 templates/dcim/module.html:31 +#: templates/dcim/moduletype/base.html:31 +msgid "Power Outlets" +msgstr "" + +#: dcim/tables/devicetypes.py:128 dcim/views.py:1017 dcim/views.py:1256 +#: dcim/views.py:1948 netbox/navigation/menu.py:83 +#: templates/dcim/device/base.html:40 templates/dcim/devicetype/base.html:37 +#: templates/dcim/module.html:37 templates/dcim/moduletype/base.html:37 +msgid "Front Ports" +msgstr "" + +#: dcim/tables/devicetypes.py:131 dcim/views.py:1032 dcim/views.py:1271 +#: dcim/views.py:1963 netbox/navigation/menu.py:84 +#: templates/dcim/device/base.html:43 templates/dcim/device_list.html:50 +#: templates/dcim/devicetype/base.html:40 templates/dcim/module.html:40 +#: templates/dcim/moduletype/base.html:40 +msgid "Rear Ports" +msgstr "" + +#: dcim/tables/devicetypes.py:134 dcim/views.py:1062 dcim/views.py:2001 +#: netbox/navigation/menu.py:90 templates/dcim/device/base.html:49 +#: templates/dcim/device_list.html:57 templates/dcim/devicetype/base.html:46 +msgid "Device Bays" +msgstr "" + +#: dcim/tables/devicetypes.py:137 dcim/views.py:1047 dcim/views.py:1982 +#: netbox/navigation/menu.py:89 templates/dcim/device/base.html:46 +#: templates/dcim/device_list.html:64 templates/dcim/devicetype/base.html:43 +msgid "Module Bays" +msgstr "" + +#: dcim/tables/power.py:36 netbox/navigation/menu.py:263 +#: templates/dcim/powerpanel.html:53 templates/extras/configrevision.html:59 +msgid "Power Feeds" +msgstr "" + +#: dcim/tables/power.py:80 templates/dcim/powerfeed.html:106 +msgid "Max Utilization" +msgstr "" + +#: dcim/tables/power.py:84 +msgid "Available Power (VA)" +msgstr "" + +#: dcim/tables/racks.py:29 dcim/tables/sites.py:138 +#: netbox/navigation/menu.py:25 netbox/navigation/menu.py:27 +msgid "Racks" +msgstr "" + +#: dcim/tables/racks.py:73 templates/dcim/device.html:340 +#: templates/dcim/rack.html:102 +msgid "Height" +msgstr "" + +#: dcim/tables/racks.py:85 +msgid "Space" +msgstr "" + +#: dcim/tables/racks.py:96 templates/dcim/rack.html:112 +msgid "Outer Width" +msgstr "" + +#: dcim/tables/racks.py:100 templates/dcim/rack.html:122 +msgid "Outer Depth" +msgstr "" + +#: dcim/tables/racks.py:108 +msgid "Max Weight" +msgstr "" + +#: dcim/tables/sites.py:30 dcim/tables/sites.py:57 +#: extras/forms/filtersets.py:334 extras/forms/model_forms.py:291 +#: ipam/forms/bulk_edit.py:130 ipam/forms/model_forms.py:154 +#: ipam/tables/asn.py:65 netbox/navigation/menu.py:16 +#: netbox/navigation/menu.py:18 +msgid "Sites" +msgstr "" + +#: dcim/views.py:131 +#, python-brace-format +msgid "Disconnected {count} {type}" +msgstr "" + +#: dcim/views.py:692 netbox/navigation/menu.py:29 +msgid "Reservations" +msgstr "" + +#: dcim/views.py:711 +msgid "Non-Racked Devices" +msgstr "" + +#: dcim/views.py:2033 extras/forms/model_forms.py:351 +#: templates/extras/configcontext.html:10 +#: virtualization/forms/model_forms.py:226 virtualization/views.py:386 +msgid "Config Context" +msgstr "" + +#: dcim/views.py:2043 virtualization/views.py:396 +msgid "Render Config" +msgstr "" + +#: extras/choices.py:27 extras/forms/misc.py:14 +msgid "Text" +msgstr "" + +#: extras/choices.py:28 +msgid "Text (long)" +msgstr "" + +#: extras/choices.py:29 +msgid "Integer" +msgstr "" + +#: extras/choices.py:30 +msgid "Decimal" +msgstr "" + +#: extras/choices.py:31 +msgid "Boolean (true/false)" +msgstr "" + +#: extras/choices.py:32 +msgid "Date" +msgstr "" + +#: extras/choices.py:33 +msgid "Date & time" +msgstr "" + +#: extras/choices.py:35 +msgid "JSON" +msgstr "" + +#: extras/choices.py:36 +msgid "Selection" +msgstr "" + +#: extras/choices.py:37 +msgid "Multiple selection" +msgstr "" + +#: extras/choices.py:39 +msgid "Multiple objects" +msgstr "" + +#: extras/choices.py:50 templates/extras/customfield.html:69 +#: wireless/choices.py:27 +msgid "Disabled" +msgstr "" + +#: extras/choices.py:51 +msgid "Loose" +msgstr "" + +#: extras/choices.py:52 +msgid "Exact" +msgstr "" + +#: extras/choices.py:64 +msgid "Read/write" +msgstr "" + +#: extras/choices.py:65 +msgid "Read-only" +msgstr "" + +#: extras/choices.py:66 +msgid "Hidden" +msgstr "" + +#: extras/choices.py:67 +msgid "Hidden (if unset)" +msgstr "" + +#: extras/choices.py:94 templates/tenancy/contact.html:58 +#: tenancy/forms/bulk_edit.py:117 wireless/forms/model_forms.py:159 +msgid "Link" +msgstr "" + +#: extras/choices.py:108 +msgid "Newest" +msgstr "" + +#: extras/choices.py:109 +msgid "Oldest" +msgstr "" + +#: extras/choices.py:125 templates/generic/object.html:51 +msgid "Updated" +msgstr "" + +#: extras/choices.py:126 +msgid "Deleted" +msgstr "" + +#: extras/choices.py:143 extras/choices.py:165 +msgid "Info" +msgstr "" + +#: extras/choices.py:144 extras/choices.py:164 +msgid "Success" +msgstr "" + +#: extras/choices.py:145 extras/choices.py:166 +msgid "Warning" +msgstr "" + +#: extras/choices.py:146 +msgid "Danger" +msgstr "" + +#: extras/choices.py:163 utilities/choices.py:190 +msgid "Default" +msgstr "" + +#: extras/choices.py:167 +msgid "Failure" +msgstr "" + +#: extras/choices.py:174 +msgid "Hourly" +msgstr "" + +#: extras/choices.py:175 +msgid "12 hours" +msgstr "" + +#: extras/choices.py:176 +msgid "Daily" +msgstr "" + +#: extras/choices.py:177 +msgid "Weekly" +msgstr "" + +#: extras/choices.py:178 +msgid "30 days" +msgstr "" + +#: extras/choices.py:243 extras/tables/tables.py:283 +#: templates/dcim/virtualchassis_edit.html:108 templates/extras/webhook.html:33 +#: templates/generic/bulk_add_component.html:56 +#: templates/generic/object_edit.html:29 templates/generic/object_edit.html:70 +#: templates/ipam/inc/ipaddress_edit_header.html:10 +msgid "Create" +msgstr "" + +#: extras/choices.py:244 extras/tables/tables.py:286 +#: templates/extras/webhook.html:37 +msgid "Update" +msgstr "" + +#: extras/choices.py:245 extras/tables/tables.py:289 +#: templates/circuits/inc/circuit_termination.html:22 +#: templates/dcim/devicetype/component_templates.html:24 +#: templates/dcim/inc/panels/inventory_items.html:29 +#: templates/dcim/moduletype/component_templates.html:24 +#: templates/dcim/powerpanel.html:71 templates/extras/report_list.html:34 +#: templates/extras/script_list.html:33 templates/extras/webhook.html:41 +#: templates/generic/bulk_delete.html:18 templates/generic/bulk_delete.html:45 +#: templates/generic/object_delete.html:15 templates/htmx/delete_form.html:23 +#: templates/ipam/inc/panels/fhrp_groups.html:35 +#: templates/users/objectpermission.html:49 +#: utilities/templates/buttons/delete.html:9 +msgid "Delete" +msgstr "" + +#: extras/choices.py:269 utilities/choices.py:143 utilities/choices.py:191 +msgid "Blue" +msgstr "" + +#: extras/choices.py:270 utilities/choices.py:142 utilities/choices.py:192 +msgid "Indigo" +msgstr "" + +#: extras/choices.py:271 utilities/choices.py:140 utilities/choices.py:193 +msgid "Purple" +msgstr "" + +#: extras/choices.py:272 utilities/choices.py:137 utilities/choices.py:194 +msgid "Pink" +msgstr "" + +#: extras/choices.py:273 utilities/choices.py:136 utilities/choices.py:195 +msgid "Red" +msgstr "" + +#: extras/choices.py:274 utilities/choices.py:154 utilities/choices.py:196 +msgid "Orange" +msgstr "" + +#: extras/choices.py:275 utilities/choices.py:152 utilities/choices.py:197 +msgid "Yellow" +msgstr "" + +#: extras/choices.py:276 utilities/choices.py:149 utilities/choices.py:198 +msgid "Green" +msgstr "" + +#: extras/choices.py:277 utilities/choices.py:146 utilities/choices.py:199 +msgid "Teal" +msgstr "" + +#: extras/choices.py:278 utilities/choices.py:145 utilities/choices.py:200 +msgid "Cyan" +msgstr "" + +#: extras/choices.py:279 utilities/choices.py:201 +msgid "Gray" +msgstr "" + +#: extras/choices.py:280 utilities/choices.py:160 utilities/choices.py:202 +msgid "Black" +msgstr "" + +#: extras/choices.py:281 utilities/choices.py:161 utilities/choices.py:203 +msgid "White" +msgstr "" + +#: extras/dashboard/forms.py:38 +msgid "Widget type" +msgstr "" + +#: extras/dashboard/widgets.py:146 +msgid "Note" +msgstr "" + +#: extras/dashboard/widgets.py:147 +msgid "Display some arbitrary custom content. Markdown is supported." +msgstr "" + +#: extras/dashboard/widgets.py:160 +msgid "Object Counts" +msgstr "" + +#: extras/dashboard/widgets.py:161 +msgid "" +"Display a set of NetBox models and the number of objects created for each " +"type." +msgstr "" + +#: extras/dashboard/widgets.py:171 +msgid "Filters to apply when counting the number of objects" +msgstr "" + +#: extras/dashboard/widgets.py:207 +msgid "Object List" +msgstr "" + +#: extras/dashboard/widgets.py:208 +msgid "Display an arbitrary list of objects." +msgstr "" + +#: extras/dashboard/widgets.py:221 +msgid "The default number of objects to display" +msgstr "" + +#: extras/dashboard/widgets.py:268 +msgid "RSS Feed" +msgstr "" + +#: extras/dashboard/widgets.py:273 +msgid "Embed an RSS feed from an external website." +msgstr "" + +#: extras/dashboard/widgets.py:280 +msgid "Feed URL" +msgstr "" + +#: extras/dashboard/widgets.py:285 +msgid "The maximum number of objects to display" +msgstr "" + +#: extras/dashboard/widgets.py:290 +msgid "How long to stored the cached content (in seconds)" +msgstr "" + +#: extras/dashboard/widgets.py:342 templates/account/base.html:10 +#: templates/account/bookmarks.html:7 templates/inc/profile_button.html:29 +msgid "Bookmarks" +msgstr "" + +#: extras/dashboard/widgets.py:346 +msgid "Show your personal bookmarks" +msgstr "" + +#: extras/filtersets.py:176 extras/filtersets.py:511 extras/filtersets.py:539 +msgid "Data file (ID)" +msgstr "" + +#: extras/filtersets.py:448 virtualization/forms/filtersets.py:111 +msgid "Cluster type" +msgstr "" + +#: extras/filtersets.py:454 virtualization/filtersets.py:93 +#: virtualization/filtersets.py:143 +msgid "Cluster type (slug)" +msgstr "" + +#: extras/filtersets.py:459 ipam/forms/bulk_edit.py:477 +#: ipam/forms/model_forms.py:587 virtualization/forms/filtersets.py:105 +msgid "Cluster group" +msgstr "" + +#: extras/filtersets.py:465 virtualization/filtersets.py:132 +msgid "Cluster group (slug)" +msgstr "" + +#: extras/filtersets.py:475 tenancy/forms/forms.py:16 tenancy/forms/forms.py:39 +msgid "Tenant group" +msgstr "" + +#: extras/filtersets.py:481 tenancy/filtersets.py:151 tenancy/filtersets.py:171 +msgid "Tenant group (slug)" +msgstr "" + +#: extras/filtersets.py:497 templates/extras/tag.html:12 +msgid "Tag" +msgstr "" + +#: extras/filtersets.py:503 +msgid "Tag (slug)" +msgstr "" + +#: extras/filtersets.py:563 extras/forms/filtersets.py:413 +msgid "Has local config context data" +msgstr "" + +#: extras/filtersets.py:588 +msgid "User name" +msgstr "" + +#: extras/forms/bulk_edit.py:31 extras/forms/filtersets.py:58 +msgid "Group name" +msgstr "" + +#: extras/forms/bulk_edit.py:39 extras/forms/filtersets.py:66 +#: extras/tables/tables.py:72 templates/extras/customfield.html:39 +#: templates/generic/bulk_import.html:116 +msgid "Required" +msgstr "" + +#: extras/forms/bulk_edit.py:52 extras/forms/bulk_import.py:56 +#: extras/forms/filtersets.py:80 extras/models/customfields.py:187 +msgid "UI visibility" +msgstr "" + +#: extras/forms/bulk_edit.py:58 extras/forms/filtersets.py:83 +msgid "Is cloneable" +msgstr "" + +#: extras/forms/bulk_edit.py:97 extras/forms/filtersets.py:123 +msgid "New window" +msgstr "" + +#: extras/forms/bulk_edit.py:106 +msgid "Button class" +msgstr "" + +#: extras/forms/bulk_edit.py:123 extras/forms/filtersets.py:161 +#: extras/models/models.py:356 +msgid "MIME type" +msgstr "" + +#: extras/forms/bulk_edit.py:128 extras/forms/filtersets.py:164 +msgid "File extension" +msgstr "" + +#: extras/forms/bulk_edit.py:133 extras/forms/filtersets.py:168 +msgid "As attachment" +msgstr "" + +#: extras/forms/bulk_edit.py:161 extras/forms/filtersets.py:210 +#: extras/tables/tables.py:236 templates/extras/savedfilter.html:30 +msgid "Shared" +msgstr "" + +#: extras/forms/bulk_edit.py:182 +msgid "On create" +msgstr "" + +#: extras/forms/bulk_edit.py:187 +msgid "On update" +msgstr "" + +#: extras/forms/bulk_edit.py:192 +msgid "On delete" +msgstr "" + +#: extras/forms/bulk_edit.py:197 +msgid "On job start" +msgstr "" + +#: extras/forms/bulk_edit.py:202 +msgid "On job end" +msgstr "" + +#: extras/forms/bulk_edit.py:209 extras/forms/filtersets.py:239 +#: extras/models/models.py:100 +msgid "HTTP method" +msgstr "" + +#: extras/forms/bulk_edit.py:213 templates/extras/webhook.html:66 +msgid "Payload URL" +msgstr "" + +#: extras/forms/bulk_edit.py:218 extras/models/models.py:146 +msgid "SSL verification" +msgstr "" + +#: extras/forms/bulk_edit.py:221 templates/extras/webhook.html:74 +msgid "Secret" +msgstr "" + +#: extras/forms/bulk_edit.py:226 +msgid "CA file path" +msgstr "" + +#: extras/forms/bulk_edit.py:261 +msgid "Is active" +msgstr "" + +#: extras/forms/bulk_import.py:31 extras/forms/bulk_import.py:91 +#: extras/forms/bulk_import.py:107 extras/forms/bulk_import.py:131 +#: extras/forms/bulk_import.py:145 extras/forms/filtersets.py:111 +#: extras/forms/filtersets.py:157 extras/forms/filtersets.py:198 +#: extras/forms/model_forms.py:46 extras/forms/model_forms.py:119 +#: extras/forms/model_forms.py:147 extras/forms/model_forms.py:189 +#: extras/forms/model_forms.py:227 +msgid "Content types" +msgstr "" + +#: extras/forms/bulk_import.py:34 extras/forms/bulk_import.py:94 +#: extras/forms/bulk_import.py:110 extras/forms/bulk_import.py:133 +#: extras/forms/bulk_import.py:148 tenancy/forms/bulk_import.py:96 +msgid "One or more assigned object types" +msgstr "" + +#: extras/forms/bulk_import.py:39 +msgid "Field data type (e.g. text, integer, etc.)" +msgstr "" + +#: extras/forms/bulk_import.py:42 extras/forms/filtersets.py:50 +#: extras/forms/filtersets.py:234 extras/forms/model_forms.py:51 +#: extras/forms/model_forms.py:215 tenancy/forms/filtersets.py:93 +msgid "Object type" +msgstr "" + +#: extras/forms/bulk_import.py:46 +msgid "Object type (for object or multi-object fields)" +msgstr "" + +#: extras/forms/bulk_import.py:49 extras/forms/filtersets.py:75 +msgid "Choice set" +msgstr "" + +#: extras/forms/bulk_import.py:53 +msgid "Choice set (for selection fields)" +msgstr "" + +#: extras/forms/bulk_import.py:58 +msgid "How the custom field is displayed in the user interface" +msgstr "" + +#: extras/forms/bulk_import.py:74 +msgid "The base set of predefined choices to use (if any)" +msgstr "" + +#: extras/forms/bulk_import.py:79 +msgid "Comma-separated list of field choices" +msgstr "" + +#: extras/forms/bulk_import.py:174 +msgid "Assigned object type" +msgstr "" + +#: extras/forms/bulk_import.py:179 +msgid "The classification of entry" +msgstr "" + +#: extras/forms/filtersets.py:55 +msgid "Field type" +msgstr "" + +#: extras/forms/filtersets.py:94 extras/tables/tables.py:87 +#: templates/generic/bulk_import.html:148 +msgid "Choices" +msgstr "" + +#: extras/forms/filtersets.py:138 extras/forms/filtersets.py:302 +#: extras/forms/filtersets.py:392 extras/forms/model_forms.py:346 +#: templates/core/job.html:80 templates/extras/configcontext.html:86 +msgid "Data" +msgstr "" + +#: extras/forms/filtersets.py:149 extras/forms/filtersets.py:316 +#: extras/forms/filtersets.py:402 utilities/choices.py:219 +#: utilities/forms/bulk_import.py:27 +msgid "Data file" +msgstr "" + +#: extras/forms/filtersets.py:182 +msgid "Content type" +msgstr "" + +#: extras/forms/filtersets.py:229 extras/forms/model_forms.py:234 +#: templates/extras/webhook.html:28 +msgid "Events" +msgstr "" + +#: extras/forms/filtersets.py:253 +msgid "Object creations" +msgstr "" + +#: extras/forms/filtersets.py:260 +msgid "Object updates" +msgstr "" + +#: extras/forms/filtersets.py:267 +msgid "Object deletions" +msgstr "" + +#: extras/forms/filtersets.py:274 +msgid "Job starts" +msgstr "" + +#: extras/forms/filtersets.py:281 extras/forms/model_forms.py:250 +msgid "Job terminations" +msgstr "" + +#: extras/forms/filtersets.py:290 +msgid "Tagged object type" +msgstr "" + +#: extras/forms/filtersets.py:295 +msgid "Allowed object type" +msgstr "" + +#: extras/forms/filtersets.py:324 extras/forms/model_forms.py:281 +#: netbox/navigation/menu.py:19 +msgid "Regions" +msgstr "" + +#: extras/forms/filtersets.py:329 extras/forms/model_forms.py:286 +msgid "Site groups" +msgstr "" + +#: extras/forms/filtersets.py:339 extras/forms/model_forms.py:296 +#: netbox/navigation/menu.py:21 +msgid "Locations" +msgstr "" + +#: extras/forms/filtersets.py:344 extras/forms/model_forms.py:301 +msgid "Device types" +msgstr "" + +#: extras/forms/filtersets.py:349 extras/forms/model_forms.py:306 +msgid "Roles" +msgstr "" + +#: extras/forms/filtersets.py:359 extras/forms/model_forms.py:316 +msgid "Cluster types" +msgstr "" + +#: extras/forms/filtersets.py:365 extras/forms/model_forms.py:321 +msgid "Cluster groups" +msgstr "" + +#: extras/forms/filtersets.py:370 extras/forms/model_forms.py:326 +#: netbox/navigation/menu.py:224 netbox/navigation/menu.py:226 +#: templates/virtualization/clustertype.html:33 +#: virtualization/tables/clusters.py:23 virtualization/tables/clusters.py:45 +msgid "Clusters" +msgstr "" + +#: extras/forms/filtersets.py:375 extras/forms/model_forms.py:331 +msgid "Tenant groups" +msgstr "" + +#: extras/forms/filtersets.py:429 extras/forms/filtersets.py:470 +msgid "After" +msgstr "" + +#: extras/forms/filtersets.py:434 extras/forms/filtersets.py:475 +msgid "Before" +msgstr "" + +#: extras/forms/filtersets.py:465 extras/tables/tables.py:426 +#: templates/extras/htmx/report_result.html:43 +#: templates/extras/objectchange.html:34 +msgid "Time" +msgstr "" + +#: extras/forms/filtersets.py:479 extras/tables/tables.py:440 +#: templates/extras/objectchange.html:50 +msgid "Action" +msgstr "" + +#: extras/forms/mixins.py:71 extras/forms/model_forms.py:195 +#: templates/extras/savedfilter.html:10 +msgid "Saved Filter" +msgstr "" + +#: extras/forms/model_forms.py:56 +msgid "Type of the related object (for object/multi-object fields only)" +msgstr "" + +#: extras/forms/model_forms.py:64 templates/extras/customfield.html:11 +msgid "Custom Field" +msgstr "" + +#: extras/forms/model_forms.py:67 templates/extras/customfield.html:60 +msgid "Behavior" +msgstr "" + +#: extras/forms/model_forms.py:68 +msgid "Values" +msgstr "" + +#: extras/forms/model_forms.py:69 extras/forms/model_forms.py:494 +#: templates/extras/configrevision.html:147 +msgid "Validation" +msgstr "" + +#: extras/forms/model_forms.py:77 +msgid "" +"The type of data stored in this field. For object/multi-object fields, " +"select the related object type below." +msgstr "" + +#: extras/forms/model_forms.py:80 +msgid "" +"This will be displayed as help text for the form field. Markdown is " +"supported." +msgstr "" + +#: extras/forms/model_forms.py:97 +msgid "" +"Enter one choice per line. An optional label may be specified for each " +"choice by appending it with a comma. Example:" +msgstr "" + +#: extras/forms/model_forms.py:125 templates/extras/customlink.html:10 +msgid "Custom Link" +msgstr "" + +#: extras/forms/model_forms.py:126 +msgid "Templates" +msgstr "" + +#: extras/forms/model_forms.py:138 +msgid "" +"Jinja2 template code for the link text. Reference the object as " +"{{ object }}. Links which render as empty text will not be " +"displayed." +msgstr "" + +#: extras/forms/model_forms.py:141 +msgid "" +"Jinja2 template code for the link URL. Reference the object as " +"{{ object }}." +msgstr "" + +#: extras/forms/model_forms.py:152 extras/forms/model_forms.py:397 +msgid "Template code" +msgstr "" + +#: extras/forms/model_forms.py:158 templates/extras/exporttemplate.html:17 +msgid "Export Template" +msgstr "" + +#: extras/forms/model_forms.py:160 +msgid "Rendering" +msgstr "" + +#: extras/forms/model_forms.py:174 extras/forms/model_forms.py:422 +msgid "Template content is populated from the remote source selected below." +msgstr "" + +#: extras/forms/model_forms.py:181 extras/forms/model_forms.py:429 +msgid "Must specify either local content or a data file" +msgstr "" + +#: extras/forms/model_forms.py:233 templates/extras/webhook.html:11 +msgid "Webhook" +msgstr "" + +#: extras/forms/model_forms.py:235 templates/extras/webhook.html:57 +msgid "HTTP Request" +msgstr "" + +#: extras/forms/model_forms.py:238 templates/extras/webhook.html:116 +msgid "Conditions" +msgstr "" + +#: extras/forms/model_forms.py:239 templates/extras/webhook.html:82 +msgid "SSL" +msgstr "" + +#: extras/forms/model_forms.py:246 +msgid "Creations" +msgstr "" + +#: extras/forms/model_forms.py:247 +msgid "Updates" +msgstr "" + +#: extras/forms/model_forms.py:248 +msgid "Deletions" +msgstr "" + +#: extras/forms/model_forms.py:249 +msgid "Job executions" +msgstr "" + +#: extras/forms/model_forms.py:262 users/forms/model_forms.py:285 +msgid "Object types" +msgstr "" + +#: extras/forms/model_forms.py:336 netbox/navigation/menu.py:40 +#: tenancy/tables/tenants.py:22 +msgid "Tenants" +msgstr "" + +#: extras/forms/model_forms.py:353 ipam/forms/filtersets.py:145 +#: templates/extras/configcontext.html:62 templates/ipam/ipaddress.html:62 +#: templates/ipam/vlan_edit.html:30 tenancy/forms/filtersets.py:87 +#: users/forms/model_forms.py:323 +msgid "Assignment" +msgstr "" + +#: extras/forms/model_forms.py:379 +msgid "Data is populated from the remote source selected below." +msgstr "" + +#: extras/forms/model_forms.py:385 +msgid "Must specify either local data or a data file" +msgstr "" + +#: extras/forms/model_forms.py:404 templates/core/datafile.html:65 +msgid "Content" +msgstr "" + +#: extras/forms/model_forms.py:488 templates/dcim/rack_elevation_list.html:6 +#: templates/extras/configrevision.html:43 +msgid "Rack Elevations" +msgstr "" + +#: extras/forms/model_forms.py:490 netbox/navigation/menu.py:142 +#: templates/extras/configrevision.html:79 +msgid "IPAM" +msgstr "" + +#: extras/forms/model_forms.py:491 templates/extras/configrevision.html:95 +msgid "Security" +msgstr "" + +#: extras/forms/model_forms.py:492 templates/extras/configrevision.html:107 +msgid "Banners" +msgstr "" + +#: extras/forms/model_forms.py:493 templates/extras/configrevision.html:131 +msgid "Pagination" +msgstr "" + +#: extras/forms/model_forms.py:495 templates/account/preferences.html:6 +#: templates/extras/configrevision.html:159 +msgid "User Preferences" +msgstr "" + +#: extras/forms/model_forms.py:499 +msgid "Config Revision" +msgstr "" + +#: extras/forms/model_forms.py:537 +msgid "This parameter has been defined statically and cannot be modified." +msgstr "" + +#: extras/forms/model_forms.py:545 +#, python-brace-format +msgid "Current value: {value}" +msgstr "" + +#: extras/forms/model_forms.py:547 +msgid " (default)" +msgstr "" + +#: extras/forms/reports.py:18 extras/forms/scripts.py:24 +msgid "Schedule at" +msgstr "" + +#: extras/forms/reports.py:19 +msgid "Schedule execution of report to a set time" +msgstr "" + +#: extras/forms/reports.py:24 extras/forms/scripts.py:30 +msgid "Recurs every" +msgstr "" + +#: extras/forms/reports.py:28 +msgid "Interval at which this report is re-run (in minutes)" +msgstr "" + +#: extras/forms/reports.py:36 extras/forms/scripts.py:42 +#, python-brace-format +msgid " (current time: {now})" +msgstr "" + +#: extras/forms/reports.py:46 extras/forms/scripts.py:52 +msgid "Scheduled time must be in the future." +msgstr "" + +#: extras/forms/scripts.py:18 +msgid "Commit changes" +msgstr "" + +#: extras/forms/scripts.py:19 +msgid "Commit changes to the database (uncheck for a dry-run)" +msgstr "" + +#: extras/forms/scripts.py:25 +msgid "Schedule execution of script to a set time" +msgstr "" + +#: extras/forms/scripts.py:34 +msgid "Interval at which this script is re-run (in minutes)" +msgstr "" + +#: extras/models/change_logging.py:23 +msgid "time" +msgstr "" + +#: extras/models/change_logging.py:36 +msgid "user name" +msgstr "" + +#: extras/models/change_logging.py:41 +msgid "request ID" +msgstr "" + +#: extras/models/change_logging.py:46 extras/models/staging.py:69 +msgid "action" +msgstr "" + +#: extras/models/change_logging.py:80 +msgid "pre-change data" +msgstr "" + +#: extras/models/change_logging.py:86 +msgid "post-change data" +msgstr "" + +#: extras/models/change_logging.py:96 +msgid "object change" +msgstr "" + +#: extras/models/change_logging.py:97 +msgid "object changes" +msgstr "" + +#: extras/models/configs.py:130 +msgid "config context" +msgstr "" + +#: extras/models/configs.py:131 +msgid "config contexts" +msgstr "" + +#: extras/models/configs.py:149 extras/models/configs.py:205 +msgid "JSON data must be in object form. Example:" +msgstr "" + +#: extras/models/configs.py:169 +msgid "" +"Local config context data takes precedence over source contexts in the final " +"rendered config context" +msgstr "" + +#: extras/models/configs.py:224 +msgid "template code" +msgstr "" + +#: extras/models/configs.py:225 +msgid "Jinja2 template code." +msgstr "" + +#: extras/models/configs.py:228 +msgid "environment parameters" +msgstr "" + +#: extras/models/configs.py:233 +msgid "" +"Any additional parameters to pass when constructing the Jinja2 " +"environment." +msgstr "" + +#: extras/models/configs.py:240 +msgid "config template" +msgstr "" + +#: extras/models/configs.py:241 +msgid "config templates" +msgstr "" + +#: extras/models/customfields.py:66 +msgid "The object(s) to which this field applies." +msgstr "" + +#: extras/models/customfields.py:73 +msgid "The type of data this custom field holds" +msgstr "" + +#: extras/models/customfields.py:80 +msgid "The type of NetBox object this field maps to (for object fields)" +msgstr "" + +#: extras/models/customfields.py:86 +msgid "Internal field name" +msgstr "" + +#: extras/models/customfields.py:90 +msgid "Only alphanumeric characters and underscores are allowed." +msgstr "" + +#: extras/models/customfields.py:95 +msgid "Double underscores are not permitted in custom field names." +msgstr "" + +#: extras/models/customfields.py:106 +msgid "" +"Name of the field as displayed to users (if not provided, 'the field's name " +"will be used)" +msgstr "" + +#: extras/models/customfields.py:110 extras/models/models.py:264 +msgid "group name" +msgstr "" + +#: extras/models/customfields.py:113 +msgid "Custom fields within the same group will be displayed together" +msgstr "" + +#: extras/models/customfields.py:121 +msgid "required" +msgstr "" + +#: extras/models/customfields.py:123 +msgid "" +"If true, this field is required when creating new objects or editing an " +"existing object." +msgstr "" + +#: extras/models/customfields.py:126 +msgid "search weight" +msgstr "" + +#: extras/models/customfields.py:129 +msgid "" +"Weighting for search. Lower values are considered more important. Fields " +"with a search weight of zero will be ignored." +msgstr "" + +#: extras/models/customfields.py:134 +msgid "filter logic" +msgstr "" + +#: extras/models/customfields.py:138 +msgid "" +"Loose matches any instance of a given string; exact matches the entire field." +msgstr "" + +#: extras/models/customfields.py:141 +msgid "default" +msgstr "" + +#: extras/models/customfields.py:145 +msgid "" +"Default value for the field (must be a JSON value). Encapsulate strings with " +"double quotes (e.g. \"Foo\")." +msgstr "" + +#: extras/models/customfields.py:150 +msgid "display weight" +msgstr "" + +#: extras/models/customfields.py:151 +msgid "Fields with higher weights appear lower in a form." +msgstr "" + +#: extras/models/customfields.py:156 +msgid "minimum value" +msgstr "" + +#: extras/models/customfields.py:157 +msgid "Minimum allowed value (for numeric fields)" +msgstr "" + +#: extras/models/customfields.py:162 +msgid "maximum value" +msgstr "" + +#: extras/models/customfields.py:163 +msgid "Maximum allowed value (for numeric fields)" +msgstr "" + +#: extras/models/customfields.py:169 +msgid "validation regex" +msgstr "" + +#: extras/models/customfields.py:171 +#, python-brace-format +msgid "" +"Regular expression to enforce on text field values. Use ^ and $ to force " +"matching of entire string. For example, ^[A-Z]{3}$ will limit " +"values to exactly three uppercase letters." +msgstr "" + +#: extras/models/customfields.py:179 +msgid "choice set" +msgstr "" + +#: extras/models/customfields.py:188 +msgid "Specifies the visibility of custom field in the UI" +msgstr "" + +#: extras/models/customfields.py:192 +msgid "is cloneable" +msgstr "" + +#: extras/models/customfields.py:193 +msgid "Replicate this value when cloning objects" +msgstr "" + +#: extras/models/customfields.py:206 +msgid "custom field" +msgstr "" + +#: extras/models/customfields.py:207 +msgid "custom fields" +msgstr "" + +#: extras/models/customfields.py:290 +#, python-brace-format +msgid "Invalid default value \"{value}\": {error}" +msgstr "" + +#: extras/models/customfields.py:297 +msgid "A minimum value may be set only for numeric fields" +msgstr "" + +#: extras/models/customfields.py:299 +msgid "A maximum value may be set only for numeric fields" +msgstr "" + +#: extras/models/customfields.py:309 +msgid "Regular expression validation is supported only for text and URL fields" +msgstr "" + +#: extras/models/customfields.py:319 +msgid "Selection fields must specify a set of choices." +msgstr "" + +#: extras/models/customfields.py:323 +msgid "Choices may be set only on selection fields." +msgstr "" + +#: extras/models/customfields.py:330 +msgid "Object fields must define an object type." +msgstr "" + +#: extras/models/customfields.py:335 +#, python-brace-format +msgid "{type} fields may not define an object type." +msgstr "" + +#: extras/models/customfields.py:415 +msgid "True" +msgstr "" + +#: extras/models/customfields.py:416 +msgid "False" +msgstr "" + +#: extras/models/customfields.py:498 +#, python-brace-format +msgid "Values must match this regex: {regex}" +msgstr "" + +#: extras/models/customfields.py:513 +msgid "Field is set to read-only." +msgstr "" + +#: extras/models/customfields.py:595 +msgid "Value must be a string." +msgstr "" + +#: extras/models/customfields.py:597 +#, python-brace-format +msgid "Value must match regex '{regex}'" +msgstr "" + +#: extras/models/customfields.py:602 +msgid "Value must be an integer." +msgstr "" + +#: extras/models/customfields.py:605 extras/models/customfields.py:620 +#, python-brace-format +msgid "Value must be at least {minimum}" +msgstr "" + +#: extras/models/customfields.py:609 extras/models/customfields.py:624 +#, python-brace-format +msgid "Value must not exceed {maximum}" +msgstr "" + +#: extras/models/customfields.py:617 +msgid "Value must be a decimal." +msgstr "" + +#: extras/models/customfields.py:629 +msgid "Value must be true or false." +msgstr "" + +#: extras/models/customfields.py:637 +msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)." +msgstr "" + +#: extras/models/customfields.py:646 +msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)." +msgstr "" + +#: extras/models/customfields.py:653 +#, python-brace-format +msgid "Invalid choice ({value}) for choice set {choiceset}." +msgstr "" + +#: extras/models/customfields.py:663 +#, python-brace-format +msgid "Invalid choice(s) ({value}) for choice set {choiceset}." +msgstr "" + +#: extras/models/customfields.py:672 +#, python-brace-format +msgid "Value must be an object ID, not {type}" +msgstr "" + +#: extras/models/customfields.py:678 +#, python-brace-format +msgid "Value must be a list of object IDs, not {type}" +msgstr "" + +#: extras/models/customfields.py:682 +#, python-brace-format +msgid "Found invalid object ID: {id}" +msgstr "" + +#: extras/models/customfields.py:685 +msgid "Required field cannot be empty." +msgstr "" + +#: extras/models/customfields.py:704 +msgid "Base set of predefined choices (optional)" +msgstr "" + +#: extras/models/customfields.py:716 +msgid "Choices are automatically ordered alphabetically" +msgstr "" + +#: extras/models/customfields.py:723 +msgid "custom field choice set" +msgstr "" + +#: extras/models/customfields.py:724 +msgid "custom field choice sets" +msgstr "" + +#: extras/models/customfields.py:760 +msgid "Must define base or extra choices." +msgstr "" + +#: extras/models/dashboard.py:19 +msgid "layout" +msgstr "" + +#: extras/models/dashboard.py:23 +msgid "config" +msgstr "" + +#: extras/models/dashboard.py:28 +msgid "dashboard" +msgstr "" + +#: extras/models/dashboard.py:29 +msgid "dashboards" +msgstr "" + +#: extras/models/models.py:50 +msgid "object types" +msgstr "" + +#: extras/models/models.py:52 +msgid "The object(s) to which this Webhook applies." +msgstr "" + +#: extras/models/models.py:60 +msgid "on create" +msgstr "" + +#: extras/models/models.py:62 +msgid "Triggers when a matching object is created." +msgstr "" + +#: extras/models/models.py:65 +msgid "on update" +msgstr "" + +#: extras/models/models.py:67 +msgid "Triggers when a matching object is updated." +msgstr "" + +#: extras/models/models.py:70 +msgid "on delete" +msgstr "" + +#: extras/models/models.py:72 +msgid "Triggers when a matching object is deleted." +msgstr "" + +#: extras/models/models.py:75 +msgid "on job start" +msgstr "" + +#: extras/models/models.py:77 +msgid "Triggers when a job for a matching object is started." +msgstr "" + +#: extras/models/models.py:80 +msgid "on job end" +msgstr "" + +#: extras/models/models.py:82 +msgid "Triggers when a job for a matching object terminates." +msgstr "" + +#: extras/models/models.py:88 +msgid "" +"This URL will be called using the HTTP method defined when the webhook is " +"called. Jinja2 template processing is supported with the same context as the " +"request body." +msgstr "" + +#: extras/models/models.py:105 +msgid "HTTP content type" +msgstr "" + +#: extras/models/models.py:107 +msgid "" +"The complete list of official content types is available here." +msgstr "" + +#: extras/models/models.py:112 +msgid "additional headers" +msgstr "" + +#: extras/models/models.py:115 +msgid "" +"User-supplied HTTP headers to be sent with the request in addition to the " +"HTTP content type. Headers should be defined in the format Name: " +"Value. Jinja2 template processing is supported with the same context " +"as the request body (below)." +msgstr "" + +#: extras/models/models.py:121 +msgid "body template" +msgstr "" + +#: extras/models/models.py:124 +msgid "" +"Jinja2 template for a custom request body. If blank, a JSON object " +"representing the change will be included. Available context data includes: " +"event, model, timestamp, " +"username, request_id, and data." +msgstr "" + +#: extras/models/models.py:130 +msgid "secret" +msgstr "" + +#: extras/models/models.py:134 +msgid "" +"When provided, the request will include a X-Hook-Signature " +"header containing a HMAC hex digest of the payload body using the secret as " +"the key. The secret is not transmitted in the request." +msgstr "" + +#: extras/models/models.py:139 +msgid "conditions" +msgstr "" + +#: extras/models/models.py:142 +msgid "" +"A set of conditions which determine whether the webhook will be generated." +msgstr "" + +#: extras/models/models.py:147 +msgid "Enable SSL certificate verification. Disable with caution!" +msgstr "" + +#: extras/models/models.py:153 templates/extras/webhook.html:91 +msgid "CA File Path" +msgstr "" + +#: extras/models/models.py:155 +msgid "" +"The specific CA certificate file to use for SSL verification. Leave blank to " +"use the system defaults." +msgstr "" + +#: extras/models/models.py:167 +msgid "webhook" +msgstr "" + +#: extras/models/models.py:168 +msgid "webhooks" +msgstr "" + +#: extras/models/models.py:188 +msgid "" +"At least one event type must be selected: create, update, delete, job_start, " +"and/or job_end." +msgstr "" + +#: extras/models/models.py:200 +msgid "Do not specify a CA certificate file if SSL verification is disabled." +msgstr "" + +#: extras/models/models.py:240 +msgid "The object type(s) to which this link applies." +msgstr "" + +#: extras/models/models.py:252 +msgid "link text" +msgstr "" + +#: extras/models/models.py:253 +msgid "Jinja2 template code for link text" +msgstr "" + +#: extras/models/models.py:256 +msgid "link URL" +msgstr "" + +#: extras/models/models.py:257 +msgid "Jinja2 template code for link URL" +msgstr "" + +#: extras/models/models.py:267 +msgid "Links with the same group will appear as a dropdown menu" +msgstr "" + +#: extras/models/models.py:270 +msgid "button class" +msgstr "" + +#: extras/models/models.py:274 +msgid "" +"The class of the first link in a group will be used for the dropdown button" +msgstr "" + +#: extras/models/models.py:277 +msgid "new window" +msgstr "" + +#: extras/models/models.py:279 +msgid "Force link to open in a new window" +msgstr "" + +#: extras/models/models.py:288 +msgid "custom link" +msgstr "" + +#: extras/models/models.py:289 +msgid "custom links" +msgstr "" + +#: extras/models/models.py:336 +msgid "The object type(s) to which this template applies." +msgstr "" + +#: extras/models/models.py:349 +msgid "" +"Jinja2 template code. The list of objects being exported is passed as a " +"context variable named queryset." +msgstr "" + +#: extras/models/models.py:357 +msgid "Defaults to text/plain; charset=utf-8" +msgstr "" + +#: extras/models/models.py:360 +msgid "file extension" +msgstr "" + +#: extras/models/models.py:363 +msgid "Extension to append to the rendered filename" +msgstr "" + +#: extras/models/models.py:366 +msgid "as attachment" +msgstr "" + +#: extras/models/models.py:368 +msgid "Download file as attachment" +msgstr "" + +#: extras/models/models.py:377 +msgid "export template" +msgstr "" + +#: extras/models/models.py:378 +msgid "export templates" +msgstr "" + +#: extras/models/models.py:395 +#, python-brace-format +msgid "\"{name}\" is a reserved name. Please choose a different name." +msgstr "" + +#: extras/models/models.py:445 +msgid "The object type(s) to which this filter applies." +msgstr "" + +#: extras/models/models.py:477 +msgid "shared" +msgstr "" + +#: extras/models/models.py:490 +msgid "saved filter" +msgstr "" + +#: extras/models/models.py:491 +msgid "saved filters" +msgstr "" + +#: extras/models/models.py:509 +msgid "Filter parameters must be stored as a dictionary of keyword arguments." +msgstr "" + +#: extras/models/models.py:537 +msgid "image height" +msgstr "" + +#: extras/models/models.py:540 +msgid "image width" +msgstr "" + +#: extras/models/models.py:554 +msgid "image attachment" +msgstr "" + +#: extras/models/models.py:555 +msgid "image attachments" +msgstr "" + +#: extras/models/models.py:623 +msgid "kind" +msgstr "" + +#: extras/models/models.py:634 +msgid "journal entry" +msgstr "" + +#: extras/models/models.py:635 +msgid "journal entries" +msgstr "" + +#: extras/models/models.py:651 +#, python-brace-format +msgid "Journaling is not supported for this object type ({type})." +msgstr "" + +#: extras/models/models.py:690 +msgid "bookmark" +msgstr "" + +#: extras/models/models.py:691 +msgid "bookmarks" +msgstr "" + +#: extras/models/models.py:708 +msgid "comment" +msgstr "" + +#: extras/models/models.py:715 +msgid "configuration data" +msgstr "" + +#: extras/models/models.py:722 +msgid "config revision" +msgstr "" + +#: extras/models/models.py:723 +msgid "config revisions" +msgstr "" + +#: extras/models/models.py:727 +msgid "Default configuration" +msgstr "" + +#: extras/models/models.py:729 +msgid "Current configuration" +msgstr "" + +#: extras/models/models.py:730 +#, python-brace-format +msgid "Config revision #{id}" +msgstr "" + +#: extras/models/reports.py:46 +msgid "report module" +msgstr "" + +#: extras/models/reports.py:47 +msgid "report modules" +msgstr "" + +#: extras/models/scripts.py:46 +msgid "script module" +msgstr "" + +#: extras/models/scripts.py:47 +msgid "script modules" +msgstr "" + +#: extras/models/search.py:22 +msgid "timestamp" +msgstr "" + +#: extras/models/search.py:37 +msgid "field" +msgstr "" + +#: extras/models/search.py:45 +msgid "value" +msgstr "" + +#: extras/models/search.py:54 +msgid "cached value" +msgstr "" + +#: extras/models/search.py:55 +msgid "cached values" +msgstr "" + +#: extras/models/staging.py:44 +msgid "branch" +msgstr "" + +#: extras/models/staging.py:45 +msgid "branches" +msgstr "" + +#: extras/models/staging.py:94 +msgid "staged change" +msgstr "" + +#: extras/models/staging.py:95 +msgid "staged changes" +msgstr "" + +#: extras/models/tags.py:44 +msgid "The object type(s) to which this this tag can be applied." +msgstr "" + +#: extras/models/tags.py:53 +msgid "tag" +msgstr "" + +#: extras/models/tags.py:54 +msgid "tags" +msgstr "" + +#: extras/models/tags.py:80 +msgid "tagged item" +msgstr "" + +#: extras/models/tags.py:81 +msgid "tagged items" +msgstr "" + +#: extras/tables/tables.py:48 users/forms/filtersets.py:47 users/tables.py:39 +msgid "Is Active" +msgstr "" + +#: extras/tables/tables.py:69 extras/tables/tables.py:141 +#: extras/tables/tables.py:165 extras/tables/tables.py:230 +#: extras/tables/tables.py:277 +msgid "Content Types" +msgstr "" + +#: extras/tables/tables.py:75 templates/extras/customfield.html:82 +msgid "UI Visibility" +msgstr "" + +#: extras/tables/tables.py:82 templates/extras/customfield.html:48 +msgid "Choice Set" +msgstr "" + +#: extras/tables/tables.py:90 +msgid "Is Cloneable" +msgstr "" + +#: extras/tables/tables.py:120 +msgid "Count" +msgstr "" + +#: extras/tables/tables.py:123 +msgid "Order Alphabetically" +msgstr "" + +#: extras/tables/tables.py:147 templates/extras/customlink.html:34 +msgid "New Window" +msgstr "" + +#: extras/tables/tables.py:168 +msgid "As Attachment" +msgstr "" + +#: extras/tables/tables.py:175 extras/tables/tables.py:367 +#: extras/tables/tables.py:402 templates/core/datafile.html:32 +#: templates/dcim/device/render_config.html:23 +#: templates/extras/configcontext.html:40 +#: templates/extras/configtemplate.html:32 +#: templates/extras/exporttemplate.html:51 +#: templates/generic/bulk_import.html:30 +#: templates/virtualization/virtualmachine/render_config.html:23 +msgid "Data File" +msgstr "" + +#: extras/tables/tables.py:180 extras/tables/tables.py:379 +#: extras/tables/tables.py:407 +msgid "Synced" +msgstr "" + +#: extras/tables/tables.py:200 +msgid "Content Type" +msgstr "" + +#: extras/tables/tables.py:207 +msgid "Image" +msgstr "" + +#: extras/tables/tables.py:212 +msgid "Size (Bytes)" +msgstr "" + +#: extras/tables/tables.py:255 extras/tables/tables.py:326 +#: templates/extras/customfield.html:92 +#: templates/users/objectpermission.html:68 users/tables.py:83 +msgid "Object Types" +msgstr "" + +#: extras/tables/tables.py:292 +msgid "Job Start" +msgstr "" + +#: extras/tables/tables.py:295 +msgid "Job End" +msgstr "" + +#: extras/tables/tables.py:298 +msgid "SSL Validation" +msgstr "" + +#: extras/tables/tables.py:436 templates/account/profile.html:20 +#: templates/users/user.html:22 +msgid "Full Name" +msgstr "" + +#: extras/tables/tables.py:453 templates/extras/objectchange.html:72 +msgid "Request ID" +msgstr "" + +#: extras/tables/tables.py:490 +msgid "Comments (Short)" +msgstr "" + +#: extras/views.py:836 +msgid "Your dashboard has been reset." +msgstr "" + +#: ipam/api/field_serializers.py:17 +msgid "Enter a valid IPv4 or IPv6 address with optional mask." +msgstr "" + +#: ipam/api/field_serializers.py:24 +#, python-brace-format +msgid "Invalid IP address format: {data}" +msgstr "" + +#: ipam/api/field_serializers.py:37 +msgid "Enter a valid IPv4 or IPv6 prefix and mask in CIDR notation." +msgstr "" + +#: ipam/api/field_serializers.py:44 +#, python-brace-format +msgid "Invalid IP prefix format: {data}" +msgstr "" + +#: ipam/choices.py:30 +msgid "Container" +msgstr "" + +#: ipam/choices.py:72 +msgid "DHCP" +msgstr "" + +#: ipam/choices.py:73 +msgid "SLAAC" +msgstr "" + +#: ipam/choices.py:89 +msgid "Loopback" +msgstr "" + +#: ipam/choices.py:90 tenancy/choices.py:18 +msgid "Secondary" +msgstr "" + +#: ipam/choices.py:91 +msgid "Anycast" +msgstr "" + +#: ipam/choices.py:115 +msgid "Standard" +msgstr "" + +#: ipam/choices.py:120 +msgid "CheckPoint" +msgstr "" + +#: ipam/choices.py:123 +msgid "Cisco" +msgstr "" + +#: ipam/choices.py:137 +msgid "Plaintext" +msgstr "" + +#: ipam/filtersets.py:47 ipam/filtersets.py:1068 +msgid "Import target" +msgstr "" + +#: ipam/filtersets.py:53 ipam/filtersets.py:1074 +msgid "Import target (name)" +msgstr "" + +#: ipam/filtersets.py:58 ipam/filtersets.py:1079 +msgid "Export target" +msgstr "" + +#: ipam/filtersets.py:64 ipam/filtersets.py:1085 +msgid "Export target (name)" +msgstr "" + +#: ipam/filtersets.py:85 +msgid "Importing VRF" +msgstr "" + +#: ipam/filtersets.py:91 +msgid "Import VRF (RD)" +msgstr "" + +#: ipam/filtersets.py:96 +msgid "Exporting VRF" +msgstr "" + +#: ipam/filtersets.py:102 +msgid "Export VRF (RD)" +msgstr "" + +#: ipam/filtersets.py:132 ipam/filtersets.py:247 ipam/forms/model_forms.py:231 +#: ipam/tables/ip.py:211 templates/ipam/prefix.html:11 +msgid "Prefix" +msgstr "" + +#: ipam/filtersets.py:136 ipam/filtersets.py:175 ipam/filtersets.py:198 +msgid "RIR (ID)" +msgstr "" + +#: ipam/filtersets.py:142 ipam/filtersets.py:181 ipam/filtersets.py:204 +msgid "RIR (slug)" +msgstr "" + +#: ipam/filtersets.py:251 +msgid "Within prefix" +msgstr "" + +#: ipam/filtersets.py:255 +msgid "Within and including prefix" +msgstr "" + +#: ipam/filtersets.py:259 +msgid "Prefixes which contain this prefix or IP" +msgstr "" + +#: ipam/filtersets.py:338 ipam/filtersets.py:1191 +msgid "VLAN (ID)" +msgstr "" + +#: ipam/filtersets.py:342 ipam/filtersets.py:1186 +msgid "VLAN number (1-4094)" +msgstr "" + +#: ipam/filtersets.py:436 ipam/filtersets.py:440 ipam/filtersets.py:532 +#: ipam/forms/model_forms.py:446 templates/tenancy/contact.html:54 +#: tenancy/forms/bulk_edit.py:112 +msgid "Address" +msgstr "" + +#: ipam/filtersets.py:444 +msgid "Ranges which contain this prefix or IP" +msgstr "" + +#: ipam/filtersets.py:472 ipam/filtersets.py:528 +msgid "Parent prefix" +msgstr "" + +#: ipam/filtersets.py:536 ipam/forms/bulk_edit.py:328 +#: ipam/forms/filtersets.py:195 ipam/forms/filtersets.py:320 +msgid "Mask length" +msgstr "" + +#: ipam/filtersets.py:572 ipam/filtersets.py:807 ipam/filtersets.py:1026 +#: ipam/filtersets.py:1149 +msgid "Virtual machine (name)" +msgstr "" + +#: ipam/filtersets.py:577 ipam/filtersets.py:812 ipam/filtersets.py:1020 +#: ipam/filtersets.py:1154 virtualization/filtersets.py:273 +msgid "Virtual machine (ID)" +msgstr "" + +#: ipam/filtersets.py:583 ipam/filtersets.py:1160 +msgid "Interface (name)" +msgstr "" + +#: ipam/filtersets.py:588 ipam/filtersets.py:1165 +msgid "Interface (ID)" +msgstr "" + +#: ipam/filtersets.py:594 ipam/filtersets.py:1171 +msgid "VM interface (name)" +msgstr "" + +#: ipam/filtersets.py:599 +msgid "VM interface (ID)" +msgstr "" + +#: ipam/filtersets.py:604 +msgid "FHRP group (ID)" +msgstr "" + +#: ipam/filtersets.py:608 +msgid "Is assigned to an interface" +msgstr "" + +#: ipam/filtersets.py:612 +msgid "Is assigned" +msgstr "" + +#: ipam/filtersets.py:1031 +msgid "IP address (ID)" +msgstr "" + +#: ipam/filtersets.py:1037 ipam/models/ip.py:786 +msgid "IP address" +msgstr "" + +#: ipam/filtersets.py:1112 +msgid "L2VPN (slug)" +msgstr "" + +#: ipam/filtersets.py:1176 +msgid "VM Interface (ID)" +msgstr "" + +#: ipam/filtersets.py:1182 +msgid "VLAN (name)" +msgstr "" + +#: ipam/forms/bulk_create.py:14 +msgid "Address pattern" +msgstr "" + +#: ipam/forms/bulk_edit.py:87 +msgid "Is private" +msgstr "" + +#: ipam/forms/bulk_edit.py:108 ipam/forms/bulk_edit.py:137 +#: ipam/forms/bulk_edit.py:162 ipam/forms/bulk_import.py:91 +#: ipam/forms/bulk_import.py:111 ipam/forms/bulk_import.py:131 +#: ipam/forms/filtersets.py:113 ipam/forms/filtersets.py:128 +#: ipam/forms/filtersets.py:151 ipam/forms/model_forms.py:95 +#: ipam/forms/model_forms.py:110 ipam/forms/model_forms.py:132 +#: ipam/forms/model_forms.py:150 ipam/models/asns.py:31 ipam/models/asns.py:103 +#: ipam/models/ip.py:70 ipam/models/ip.py:89 ipam/tables/asn.py:20 +#: ipam/tables/asn.py:45 templates/ipam/aggregate.html:19 +#: templates/ipam/asn.html:28 templates/ipam/asnrange.html:20 +#: templates/ipam/rir.html:20 +msgid "RIR" +msgstr "" + +#: ipam/forms/bulk_edit.py:170 +msgid "Date added" +msgstr "" + +#: ipam/forms/bulk_edit.py:231 +msgid "Prefix length" +msgstr "" + +#: ipam/forms/bulk_edit.py:254 ipam/forms/filtersets.py:240 +#: templates/ipam/prefix.html:86 +msgid "Is a pool" +msgstr "" + +#: ipam/forms/bulk_edit.py:259 ipam/forms/bulk_edit.py:303 +#: ipam/models/ip.py:271 ipam/models/ip.py:538 +#, python-format +msgid "Treat as 100% utilized" +msgstr "" + +#: ipam/forms/bulk_edit.py:351 ipam/models/ip.py:771 +msgid "DNS name" +msgstr "" + +#: ipam/forms/bulk_edit.py:372 ipam/forms/bulk_edit.py:571 +#: ipam/forms/bulk_import.py:396 ipam/forms/bulk_import.py:480 +#: ipam/forms/bulk_import.py:506 ipam/forms/filtersets.py:379 +#: ipam/forms/filtersets.py:513 templates/ipam/fhrpgroup.html:23 +#: templates/ipam/inc/panels/fhrp_groups.html:11 templates/ipam/service.html:35 +#: templates/ipam/servicetemplate.html:20 +msgid "Protocol" +msgstr "" + +#: ipam/forms/bulk_edit.py:379 ipam/forms/filtersets.py:386 +#: ipam/tables/fhrp.py:22 templates/ipam/fhrpgroup.html:27 +msgid "Group ID" +msgstr "" + +#: ipam/forms/bulk_edit.py:384 ipam/forms/filtersets.py:391 +#: wireless/forms/bulk_edit.py:67 wireless/forms/bulk_edit.py:114 +#: wireless/forms/bulk_import.py:62 wireless/forms/bulk_import.py:65 +#: wireless/forms/bulk_import.py:104 wireless/forms/bulk_import.py:107 +#: wireless/forms/filtersets.py:53 wireless/forms/filtersets.py:87 +msgid "Authentication type" +msgstr "" + +#: ipam/forms/bulk_edit.py:389 ipam/forms/filtersets.py:395 +msgid "Authentication key" +msgstr "" + +#: ipam/forms/bulk_edit.py:406 ipam/forms/filtersets.py:372 +#: ipam/forms/model_forms.py:457 netbox/navigation/menu.py:356 +#: templates/ipam/fhrpgroup.html:51 +#: templates/wireless/inc/authentication_attrs.html:5 +#: wireless/forms/bulk_edit.py:90 wireless/forms/bulk_edit.py:137 +#: wireless/forms/filtersets.py:35 wireless/forms/filtersets.py:75 +#: wireless/forms/model_forms.py:56 wireless/forms/model_forms.py:161 +msgid "Authentication" +msgstr "" + +#: ipam/forms/bulk_edit.py:416 +msgid "Minimum child VLAN VID" +msgstr "" + +#: ipam/forms/bulk_edit.py:422 +msgid "Maximum child VLAN VID" +msgstr "" + +#: ipam/forms/bulk_edit.py:430 ipam/forms/model_forms.py:529 +msgid "Scope type" +msgstr "" + +#: ipam/forms/bulk_edit.py:491 ipam/forms/model_forms.py:602 +#: ipam/tables/vlans.py:71 templates/ipam/vlangroup.html:39 +msgid "Scope" +msgstr "" + +#: ipam/forms/bulk_edit.py:562 +msgid "Site & Group" +msgstr "" + +#: ipam/forms/bulk_edit.py:576 ipam/forms/model_forms.py:665 +#: ipam/forms/model_forms.py:699 ipam/tables/services.py:19 +#: ipam/tables/services.py:49 templates/ipam/service.html:39 +#: templates/ipam/servicetemplate.html:24 +msgid "Ports" +msgstr "" + +#: ipam/forms/bulk_import.py:50 +msgid "Import route targets" +msgstr "" + +#: ipam/forms/bulk_import.py:56 +msgid "Export route targets" +msgstr "" + +#: ipam/forms/bulk_import.py:94 ipam/forms/bulk_import.py:114 +#: ipam/forms/bulk_import.py:134 +msgid "Assigned RIR" +msgstr "" + +#: ipam/forms/bulk_import.py:184 +msgid "VLAN's group (if any)" +msgstr "" + +#: ipam/forms/bulk_import.py:187 ipam/forms/bulk_import.py:564 +#: ipam/forms/filtersets.py:603 ipam/forms/model_forms.py:221 +#: ipam/forms/model_forms.py:804 ipam/models/vlans.py:213 ipam/tables/ip.py:254 +#: templates/ipam/l2vpntermination_edit.html:17 templates/ipam/prefix.html:61 +#: templates/ipam/vlan.html:12 templates/ipam/vlan/base.html:6 +#: templates/ipam/vlan_edit.html:10 templates/wireless/wirelesslan.html:31 +#: wireless/forms/bulk_edit.py:54 wireless/forms/bulk_import.py:48 +#: wireless/forms/model_forms.py:49 wireless/models.py:101 +msgid "VLAN" +msgstr "" + +#: ipam/forms/bulk_import.py:310 +msgid "Parent device of assigned interface (if any)" +msgstr "" + +#: ipam/forms/bulk_import.py:313 ipam/forms/bulk_import.py:499 +#: ipam/forms/bulk_import.py:550 ipam/forms/model_forms.py:693 +#: virtualization/filtersets.py:279 virtualization/forms/bulk_edit.py:197 +#: virtualization/forms/bulk_import.py:145 +#: virtualization/forms/filtersets.py:200 +#: virtualization/forms/model_forms.py:280 +msgid "Virtual machine" +msgstr "" + +#: ipam/forms/bulk_import.py:317 +msgid "Parent VM of assigned interface (if any)" +msgstr "" + +#: ipam/forms/bulk_import.py:324 +msgid "Assigned interface" +msgstr "" + +#: ipam/forms/bulk_import.py:327 +msgid "Is primary" +msgstr "" + +#: ipam/forms/bulk_import.py:328 +msgid "Make this the primary IP for the assigned device" +msgstr "" + +#: ipam/forms/bulk_import.py:367 +msgid "No device or virtual machine specified; cannot set as primary IP" +msgstr "" + +#: ipam/forms/bulk_import.py:371 +msgid "No interface specified; cannot set as primary IP" +msgstr "" + +#: ipam/forms/bulk_import.py:400 +msgid "Auth type" +msgstr "" + +#: ipam/forms/bulk_import.py:415 +msgid "Scope type (app & model)" +msgstr "" + +#: ipam/forms/bulk_import.py:421 +#, python-brace-format +msgid "Minimum child VLAN VID (default: {minimum})" +msgstr "" + +#: ipam/forms/bulk_import.py:427 +#, python-brace-format +msgid "Maximum child VLAN VID (default: {maximum})" +msgstr "" + +#: ipam/forms/bulk_import.py:451 +msgid "Assigned VLAN group" +msgstr "" + +#: ipam/forms/bulk_import.py:482 ipam/forms/bulk_import.py:508 +msgid "IP protocol" +msgstr "" + +#: ipam/forms/bulk_import.py:496 +msgid "Required if not assigned to a VM" +msgstr "" + +#: ipam/forms/bulk_import.py:503 +msgid "Required if not assigned to a device" +msgstr "" + +#: ipam/forms/bulk_import.py:526 +msgid "L2VPN type" +msgstr "" + +#: ipam/forms/bulk_import.py:547 +msgid "Parent device (for interface)" +msgstr "" + +#: ipam/forms/bulk_import.py:554 +msgid "Parent virtual machine (for interface)" +msgstr "" + +#: ipam/forms/bulk_import.py:561 +msgid "Assigned interface (device or VM)" +msgstr "" + +#: ipam/forms/bulk_import.py:594 +msgid "Cannot import device and VM interface terminations simultaneously." +msgstr "" + +#: ipam/forms/bulk_import.py:596 +msgid "Each termination must specify either an interface or a VLAN." +msgstr "" + +#: ipam/forms/bulk_import.py:598 +msgid "Cannot assign both an interface and a VLAN." +msgstr "" + +#: ipam/forms/filtersets.py:50 ipam/forms/model_forms.py:62 +#: ipam/forms/model_forms.py:780 netbox/navigation/menu.py:177 +msgid "Route Targets" +msgstr "" + +#: ipam/forms/filtersets.py:56 ipam/forms/filtersets.py:544 +#: ipam/forms/model_forms.py:49 ipam/forms/model_forms.py:767 +msgid "Import targets" +msgstr "" + +#: ipam/forms/filtersets.py:61 ipam/forms/filtersets.py:549 +#: ipam/forms/model_forms.py:54 ipam/forms/model_forms.py:772 +msgid "Export targets" +msgstr "" + +#: ipam/forms/filtersets.py:76 +msgid "Imported by VRF" +msgstr "" + +#: ipam/forms/filtersets.py:81 +msgid "Exported by VRF" +msgstr "" + +#: ipam/forms/filtersets.py:90 ipam/tables/ip.py:89 templates/ipam/rir.html:33 +msgid "Private" +msgstr "" + +#: ipam/forms/filtersets.py:108 ipam/forms/filtersets.py:190 +#: ipam/forms/filtersets.py:265 ipam/forms/filtersets.py:315 +msgid "Address family" +msgstr "" + +#: ipam/forms/filtersets.py:122 templates/ipam/asnrange.html:26 +msgid "Range" +msgstr "" + +#: ipam/forms/filtersets.py:131 +msgid "Start" +msgstr "" + +#: ipam/forms/filtersets.py:135 +msgid "End" +msgstr "" + +#: ipam/forms/filtersets.py:185 +msgid "Search within" +msgstr "" + +#: ipam/forms/filtersets.py:206 ipam/forms/filtersets.py:331 +msgid "Present in VRF" +msgstr "" + +#: ipam/forms/filtersets.py:247 ipam/forms/filtersets.py:286 +#, python-format +msgid "Marked as 100% utilized" +msgstr "" + +#: ipam/forms/filtersets.py:301 +msgid "Device/VM" +msgstr "" + +#: ipam/forms/filtersets.py:336 +msgid "Assigned Device" +msgstr "" + +#: ipam/forms/filtersets.py:341 +msgid "Assigned VM" +msgstr "" + +#: ipam/forms/filtersets.py:355 +msgid "Assigned to an interface" +msgstr "" + +#: ipam/forms/filtersets.py:362 templates/ipam/ipaddress.html:54 +msgid "DNS Name" +msgstr "" + +#: ipam/forms/filtersets.py:404 ipam/forms/filtersets.py:496 +#: ipam/models/vlans.py:154 templates/ipam/vlan.html:34 +msgid "VLAN ID" +msgstr "" + +#: ipam/forms/filtersets.py:436 +msgid "Minimum VID" +msgstr "" + +#: ipam/forms/filtersets.py:442 +msgid "Maximum VID" +msgstr "" + +#: ipam/forms/filtersets.py:518 +msgid "Port" +msgstr "" + +#: ipam/forms/filtersets.py:558 ipam/tables/ip.py:424 +#: templates/ipam/l2vpntermination.html:19 +msgid "Assigned Object" +msgstr "" + +#: ipam/forms/filtersets.py:570 +msgid "Assigned Object Type" +msgstr "" + +#: ipam/forms/filtersets.py:612 ipam/tables/vlans.py:191 +#: templates/ipam/ipaddress_edit.html:47 +#: templates/ipam/l2vpntermination_edit.html:27 +#: templates/ipam/service_create.html:22 templates/ipam/service_edit.html:21 +#: templates/virtualization/virtualmachine.html:13 +#: templates/virtualization/vminterface.html:24 +#: virtualization/forms/filtersets.py:186 +#: virtualization/forms/model_forms.py:221 +#: virtualization/tables/virtualmachines.py:110 +msgid "Virtual Machine" +msgstr "" + +#: ipam/forms/model_forms.py:115 ipam/tables/ip.py:116 +#: templates/ipam/aggregate.html:11 templates/ipam/prefix.html:38 +msgid "Aggregate" +msgstr "" + +#: ipam/forms/model_forms.py:136 templates/ipam/asnrange.html:12 +msgid "ASN Range" +msgstr "" + +#: ipam/forms/model_forms.py:232 +msgid "Site/VLAN Assignment" +msgstr "" + +#: ipam/forms/model_forms.py:258 templates/ipam/iprange.html:11 +msgid "IP Range" +msgstr "" + +#: ipam/forms/model_forms.py:287 ipam/forms/model_forms.py:456 +#: templates/ipam/fhrpgroup.html:19 templates/ipam/ipaddress_edit.html:52 +msgid "FHRP Group" +msgstr "" + +#: ipam/forms/model_forms.py:302 +msgid "Make this the primary IP for the device/VM" +msgstr "" + +#: ipam/forms/model_forms.py:353 +msgid "An IP address can only be assigned to a single object." +msgstr "" + +#: ipam/forms/model_forms.py:359 ipam/models/ip.py:877 +msgid "" +"Cannot reassign IP address while it is designated as the primary IP for the " +"parent object" +msgstr "" + +#: ipam/forms/model_forms.py:369 +msgid "" +"Only IP addresses assigned to an interface can be designated as primary IPs." +msgstr "" + +#: ipam/forms/model_forms.py:375 +#, python-brace-format +msgid "{ip} is a network ID, which may not be assigned to an interface." +msgstr "" + +#: ipam/forms/model_forms.py:381 +#, python-brace-format +msgid "{ip} is a broadcast address, which may not be assigned to an interface." +msgstr "" + +#: ipam/forms/model_forms.py:458 +msgid "Virtual IP Address" +msgstr "" + +#: ipam/forms/model_forms.py:600 ipam/forms/model_forms.py:639 +#: ipam/tables/ip.py:250 templates/ipam/vlan_edit.html:37 +#: templates/ipam/vlangroup.html:27 +msgid "VLAN Group" +msgstr "" + +#: ipam/forms/model_forms.py:601 +msgid "Child VLANs" +msgstr "" + +#: ipam/forms/model_forms.py:670 ipam/forms/model_forms.py:704 +msgid "" +"Comma-separated list of one or more port numbers. A range may be specified " +"using a hyphen." +msgstr "" + +#: ipam/forms/model_forms.py:675 templates/ipam/servicetemplate.html:12 +msgid "Service Template" +msgstr "" + +#: ipam/forms/model_forms.py:726 +msgid "Service template" +msgstr "" + +#: ipam/forms/model_forms.py:846 +msgid "A termination must specify an interface or VLAN." +msgstr "" + +#: ipam/forms/model_forms.py:848 +msgid "" +"A termination can only have one terminating object (an interface or VLAN)." +msgstr "" + +#: ipam/models/asns.py:34 +msgid "start" +msgstr "" + +#: ipam/models/asns.py:51 +msgid "ASN range" +msgstr "" + +#: ipam/models/asns.py:52 +msgid "ASN ranges" +msgstr "" + +#: ipam/models/asns.py:72 +#, python-brace-format +msgid "Starting ASN ({start}) must be lower than ending ASN ({end})." +msgstr "" + +#: ipam/models/asns.py:104 +msgid "Regional Internet Registry responsible for this AS number space" +msgstr "" + +#: ipam/models/asns.py:109 +msgid "16- or 32-bit autonomous system number" +msgstr "" + +#: ipam/models/fhrp.py:23 +msgid "group ID" +msgstr "" + +#: ipam/models/fhrp.py:31 ipam/models/services.py:22 +msgid "protocol" +msgstr "" + +#: ipam/models/fhrp.py:39 wireless/models.py:27 +msgid "authentication type" +msgstr "" + +#: ipam/models/fhrp.py:44 +msgid "authentication key" +msgstr "" + +#: ipam/models/fhrp.py:57 +msgid "FHRP group" +msgstr "" + +#: ipam/models/fhrp.py:58 +msgid "FHRP groups" +msgstr "" + +#: ipam/models/fhrp.py:94 tenancy/models/contacts.py:133 +msgid "priority" +msgstr "" + +#: ipam/models/fhrp.py:111 +msgid "FHRP group assignment" +msgstr "" + +#: ipam/models/fhrp.py:112 +msgid "FHRP group assignments" +msgstr "" + +#: ipam/models/ip.py:64 +msgid "private" +msgstr "" + +#: ipam/models/ip.py:65 +msgid "IP space managed by this RIR is considered private" +msgstr "" + +#: ipam/models/ip.py:71 netbox/navigation/menu.py:170 +msgid "RIRs" +msgstr "" + +#: ipam/models/ip.py:83 +msgid "IPv4 or IPv6 network" +msgstr "" + +#: ipam/models/ip.py:90 +msgid "Regional Internet Registry responsible for this IP space" +msgstr "" + +#: ipam/models/ip.py:100 +msgid "date added" +msgstr "" + +#: ipam/models/ip.py:114 +msgid "aggregate" +msgstr "" + +#: ipam/models/ip.py:115 +msgid "aggregates" +msgstr "" + +#: ipam/models/ip.py:131 +msgid "Cannot create aggregate with /0 mask." +msgstr "" + +#: ipam/models/ip.py:143 +#, python-brace-format +msgid "" +"Aggregates cannot overlap. {prefix} is already covered by an existing " +"aggregate ({aggregate})." +msgstr "" + +#: ipam/models/ip.py:157 +#, python-brace-format +msgid "" +"Prefixes cannot overlap aggregates. {prefix} covers an existing aggregate " +"({aggregate})." +msgstr "" + +#: ipam/models/ip.py:199 ipam/models/ip.py:736 +msgid "role" +msgstr "" + +#: ipam/models/ip.py:200 +msgid "roles" +msgstr "" + +#: ipam/models/ip.py:216 ipam/models/ip.py:292 +msgid "prefix" +msgstr "" + +#: ipam/models/ip.py:217 +msgid "IPv4 or IPv6 network with mask" +msgstr "" + +#: ipam/models/ip.py:253 +msgid "Operational status of this prefix" +msgstr "" + +#: ipam/models/ip.py:261 +msgid "The primary function of this prefix" +msgstr "" + +#: ipam/models/ip.py:264 +msgid "is a pool" +msgstr "" + +#: ipam/models/ip.py:266 +msgid "All IP addresses within this prefix are considered usable" +msgstr "" + +#: ipam/models/ip.py:269 ipam/models/ip.py:536 +msgid "mark utilized" +msgstr "" + +#: ipam/models/ip.py:293 +msgid "prefixes" +msgstr "" + +#: ipam/models/ip.py:316 +msgid "Cannot create prefix with /0 mask." +msgstr "" + +#: ipam/models/ip.py:323 ipam/models/ip.py:853 +#, python-brace-format +msgid "VRF {vrf}" +msgstr "" + +#: ipam/models/ip.py:323 ipam/models/ip.py:853 +msgid "global table" +msgstr "" + +#: ipam/models/ip.py:325 +#, python-brace-format +msgid "Duplicate prefix found in {table}: {prefix}" +msgstr "" + +#: ipam/models/ip.py:494 +msgid "start address" +msgstr "" + +#: ipam/models/ip.py:495 ipam/models/ip.py:499 ipam/models/ip.py:711 +msgid "IPv4 or IPv6 address (with mask)" +msgstr "" + +#: ipam/models/ip.py:498 +msgid "end address" +msgstr "" + +#: ipam/models/ip.py:525 +msgid "Operational status of this range" +msgstr "" + +#: ipam/models/ip.py:533 +msgid "The primary function of this range" +msgstr "" + +#: ipam/models/ip.py:547 +msgid "IP range" +msgstr "" + +#: ipam/models/ip.py:548 +msgid "IP ranges" +msgstr "" + +#: ipam/models/ip.py:564 +msgid "Starting and ending IP address versions must match" +msgstr "" + +#: ipam/models/ip.py:570 +msgid "Starting and ending IP address masks must match" +msgstr "" + +#: ipam/models/ip.py:577 +#, python-brace-format +msgid "" +"Ending address must be lower than the starting address ({start_address})" +msgstr "" + +#: ipam/models/ip.py:589 +#, python-brace-format +msgid "Defined addresses overlap with range {overlapping_range} in VRF {vrf}" +msgstr "" + +#: ipam/models/ip.py:598 +#, python-brace-format +msgid "Defined range exceeds maximum supported size ({max_size})" +msgstr "" + +#: ipam/models/ip.py:710 tenancy/models/contacts.py:81 +msgid "address" +msgstr "" + +#: ipam/models/ip.py:733 +msgid "The operational status of this IP" +msgstr "" + +#: ipam/models/ip.py:740 +msgid "The functional role of this IP" +msgstr "" + +#: ipam/models/ip.py:764 templates/ipam/ipaddress.html:75 +msgid "NAT (inside)" +msgstr "" + +#: ipam/models/ip.py:765 +msgid "The IP for which this address is the \"outside\" IP" +msgstr "" + +#: ipam/models/ip.py:772 +msgid "Hostname or FQDN (not case-sensitive)" +msgstr "" + +#: ipam/models/ip.py:787 ipam/models/services.py:94 +msgid "IP addresses" +msgstr "" + +#: ipam/models/ip.py:843 +msgid "Cannot create IP address with /0 mask." +msgstr "" + +#: ipam/models/ip.py:855 +#, python-brace-format +msgid "Duplicate IP address found in {table}: {ipaddress}" +msgstr "" + +#: ipam/models/ip.py:884 +msgid "Only IPv6 addresses can be assigned SLAAC status" +msgstr "" + +#: ipam/models/l2vpn.py:64 netbox/navigation/menu.py:205 +msgid "L2VPNs" +msgstr "" + +#: ipam/models/l2vpn.py:113 +msgid "L2VPN termination" +msgstr "" + +#: ipam/models/l2vpn.py:114 +msgid "L2VPN terminations" +msgstr "" + +#: ipam/models/l2vpn.py:132 +#, python-brace-format +msgid "L2VPN Termination already assigned ({assigned_object})" +msgstr "" + +#: ipam/models/l2vpn.py:144 +#, python-brace-format +msgid "" +"{l2vpn_type} L2VPNs cannot have more than two terminations; found " +"{terminations_count} already defined." +msgstr "" + +#: ipam/models/services.py:33 +msgid "port numbers" +msgstr "" + +#: ipam/models/services.py:59 +msgid "service template" +msgstr "" + +#: ipam/models/services.py:60 +msgid "service templates" +msgstr "" + +#: ipam/models/services.py:95 +msgid "The specific IP addresses (if any) to which this service is bound" +msgstr "" + +#: ipam/models/services.py:102 +msgid "service" +msgstr "" + +#: ipam/models/services.py:103 +msgid "services" +msgstr "" + +#: ipam/models/services.py:117 +msgid "" +"A service cannot be associated with both a device and a virtual machine." +msgstr "" + +#: ipam/models/services.py:119 +msgid "A service must be associated with either a device or a virtual machine." +msgstr "" + +#: ipam/models/vlans.py:50 +msgid "minimum VLAN ID" +msgstr "" + +#: ipam/models/vlans.py:56 +msgid "Lowest permissible ID of a child VLAN" +msgstr "" + +#: ipam/models/vlans.py:59 +msgid "maximum VLAN ID" +msgstr "" + +#: ipam/models/vlans.py:65 +msgid "Highest permissible ID of a child VLAN" +msgstr "" + +#: ipam/models/vlans.py:83 +msgid "VLAN groups" +msgstr "" + +#: ipam/models/vlans.py:93 +msgid "Cannot set scope_type without scope_id." +msgstr "" + +#: ipam/models/vlans.py:95 +msgid "Cannot set scope_id without scope_type." +msgstr "" + +#: ipam/models/vlans.py:100 +msgid "Maximum child VID must be greater than or equal to minimum child VID" +msgstr "" + +#: ipam/models/vlans.py:143 +msgid "The specific site to which this VLAN is assigned (if any)" +msgstr "" + +#: ipam/models/vlans.py:151 +msgid "VLAN group (optional)" +msgstr "" + +#: ipam/models/vlans.py:159 +msgid "Numeric VLAN ID (1-4094)" +msgstr "" + +#: ipam/models/vlans.py:177 +msgid "Operational status of this VLAN" +msgstr "" + +#: ipam/models/vlans.py:185 +msgid "The primary function of this VLAN" +msgstr "" + +#: ipam/models/vlans.py:214 ipam/tables/ip.py:175 ipam/tables/vlans.py:78 +#: ipam/views.py:942 netbox/navigation/menu.py:181 +#: netbox/navigation/menu.py:183 +msgid "VLANs" +msgstr "" + +#: ipam/models/vlans.py:229 +#, python-brace-format +msgid "" +"VLAN is assigned to group {group} (scope: {scope}); cannot also assign to " +"site {site}." +msgstr "" + +#: ipam/models/vlans.py:237 +#, python-brace-format +msgid "VID must be between {minimum} and {maximum} for VLANs in group {group}" +msgstr "" + +#: ipam/models/vrfs.py:30 +msgid "route distinguisher" +msgstr "" + +#: ipam/models/vrfs.py:31 +msgid "Unique route distinguisher (as defined in RFC 4364)" +msgstr "" + +#: ipam/models/vrfs.py:42 +msgid "enforce unique space" +msgstr "" + +#: ipam/models/vrfs.py:43 +msgid "Prevent duplicate prefixes/IP addresses within this VRF" +msgstr "" + +#: ipam/models/vrfs.py:63 netbox/navigation/menu.py:174 +#: netbox/navigation/menu.py:176 +msgid "VRFs" +msgstr "" + +#: ipam/models/vrfs.py:82 +msgid "Route target value (formatted in accordance with RFC 4360)" +msgstr "" + +#: ipam/models/vrfs.py:94 +msgid "route target" +msgstr "" + +#: ipam/models/vrfs.py:95 +msgid "route targets" +msgstr "" + +#: ipam/tables/asn.py:51 +msgid "ASDOT" +msgstr "" + +#: ipam/tables/asn.py:56 +msgid "Site Count" +msgstr "" + +#: ipam/tables/asn.py:61 +msgid "Provider Count" +msgstr "" + +#: ipam/tables/ip.py:94 netbox/navigation/menu.py:167 +#: netbox/navigation/menu.py:169 +msgid "Aggregates" +msgstr "" + +#: ipam/tables/ip.py:124 +msgid "Added" +msgstr "" + +#: ipam/tables/ip.py:127 ipam/tables/ip.py:165 ipam/tables/vlans.py:138 +#: ipam/views.py:351 netbox/navigation/menu.py:153 +#: netbox/navigation/menu.py:155 templates/ipam/vlan.html:87 +msgid "Prefixes" +msgstr "" + +#: ipam/tables/ip.py:130 ipam/tables/ip.py:267 ipam/tables/ip.py:320 +#: ipam/tables/vlans.py:82 templates/dcim/device.html:280 +#: templates/ipam/aggregate.html:25 templates/ipam/iprange.html:32 +#: templates/ipam/prefix.html:100 +msgid "Utilization" +msgstr "" + +#: ipam/tables/ip.py:170 netbox/navigation/menu.py:149 +msgid "IP Ranges" +msgstr "" + +#: ipam/tables/ip.py:220 +msgid "Prefix (Flat)" +msgstr "" + +#: ipam/tables/ip.py:224 templates/dcim/rack_edit.html:52 +msgid "Depth" +msgstr "" + +#: ipam/tables/ip.py:233 +msgid "Children" +msgstr "" + +#: ipam/tables/ip.py:261 +msgid "Pool" +msgstr "" + +#: ipam/tables/ip.py:264 ipam/tables/ip.py:317 +msgid "Marked Utilized" +msgstr "" + +#: ipam/tables/ip.py:301 +msgid "Start address" +msgstr "" + +#: ipam/tables/ip.py:379 +msgid "NAT (Inside)" +msgstr "" + +#: ipam/tables/ip.py:384 +msgid "NAT (Outside)" +msgstr "" + +#: ipam/tables/ip.py:389 +msgid "Assigned" +msgstr "" + +#: ipam/tables/l2vpn.py:27 ipam/tables/vrfs.py:36 +msgid "Import Targets" +msgstr "" + +#: ipam/tables/l2vpn.py:32 ipam/tables/vrfs.py:41 +msgid "Export Targets" +msgstr "" + +#: ipam/tables/l2vpn.py:69 +msgid "Object Parent" +msgstr "" + +#: ipam/tables/l2vpn.py:74 +msgid "Object Site" +msgstr "" + +#: ipam/tables/vlans.py:68 +msgid "Scope Type" +msgstr "" + +#: ipam/tables/vlans.py:107 ipam/tables/vlans.py:210 +#: templates/dcim/inc/interface_vlans_table.html:4 +msgid "VID" +msgstr "" + +#: ipam/tables/vrfs.py:30 +msgid "RD" +msgstr "" + +#: ipam/tables/vrfs.py:33 +msgid "Unique" +msgstr "" + +#: ipam/views.py:538 +msgid "Child Prefixes" +msgstr "" + +#: ipam/views.py:573 +msgid "Child Ranges" +msgstr "" + +#: ipam/views.py:870 +msgid "Related IPs" +msgstr "" + +#: ipam/views.py:1093 +msgid "Device Interfaces" +msgstr "" + +#: ipam/views.py:1111 +msgid "VM Interfaces" +msgstr "" + +#: netbox/config/parameters.py:22 templates/extras/configrevision.html:111 +msgid "Login banner" +msgstr "" + +#: netbox/config/parameters.py:24 +msgid "Additional content to display on the login page" +msgstr "" + +#: netbox/config/parameters.py:33 templates/extras/configrevision.html:115 +msgid "Maintenance banner" +msgstr "" + +#: netbox/config/parameters.py:35 +msgid "Additional content to display when in maintenance mode" +msgstr "" + +#: netbox/config/parameters.py:44 templates/extras/configrevision.html:119 +msgid "Top banner" +msgstr "" + +#: netbox/config/parameters.py:46 +msgid "Additional content to display at the top of every page" +msgstr "" + +#: netbox/config/parameters.py:55 templates/extras/configrevision.html:123 +msgid "Bottom banner" +msgstr "" + +#: netbox/config/parameters.py:57 +msgid "Additional content to display at the bottom of every page" +msgstr "" + +#: netbox/config/parameters.py:68 +msgid "Globally unique IP space" +msgstr "" + +#: netbox/config/parameters.py:70 +msgid "Enforce unique IP addressing within the global table" +msgstr "" + +#: netbox/config/parameters.py:75 templates/extras/configrevision.html:87 +msgid "Prefer IPv4" +msgstr "" + +#: netbox/config/parameters.py:77 +msgid "Prefer IPv4 addresses over IPv6" +msgstr "" + +#: netbox/config/parameters.py:84 +msgid "Rack unit height" +msgstr "" + +#: netbox/config/parameters.py:86 +msgid "Default unit height for rendered rack elevations" +msgstr "" + +#: netbox/config/parameters.py:91 +msgid "Rack unit width" +msgstr "" + +#: netbox/config/parameters.py:93 +msgid "Default unit width for rendered rack elevations" +msgstr "" + +#: netbox/config/parameters.py:100 +msgid "Powerfeed voltage" +msgstr "" + +#: netbox/config/parameters.py:102 +msgid "Default voltage for powerfeeds" +msgstr "" + +#: netbox/config/parameters.py:107 +msgid "Powerfeed amperage" +msgstr "" + +#: netbox/config/parameters.py:109 +msgid "Default amperage for powerfeeds" +msgstr "" + +#: netbox/config/parameters.py:114 +msgid "Powerfeed max utilization" +msgstr "" + +#: netbox/config/parameters.py:116 +msgid "Default max utilization for powerfeeds" +msgstr "" + +#: netbox/config/parameters.py:123 templates/extras/configrevision.html:99 +msgid "Allowed URL schemes" +msgstr "" + +#: netbox/config/parameters.py:128 +msgid "Permitted schemes for URLs in user-provided content" +msgstr "" + +#: netbox/config/parameters.py:136 +msgid "Default page size" +msgstr "" + +#: netbox/config/parameters.py:142 +msgid "Maximum page size" +msgstr "" + +#: netbox/config/parameters.py:150 templates/extras/configrevision.html:151 +msgid "Custom validators" +msgstr "" + +#: netbox/config/parameters.py:152 +msgid "Custom validation rules (JSON)" +msgstr "" + +#: netbox/config/parameters.py:164 +msgid "Default preferences" +msgstr "" + +#: netbox/config/parameters.py:166 +msgid "Default preferences for new users" +msgstr "" + +#: netbox/config/parameters.py:173 templates/extras/configrevision.html:175 +msgid "Maintenance mode" +msgstr "" + +#: netbox/config/parameters.py:175 +msgid "Enable maintenance mode" +msgstr "" + +#: netbox/config/parameters.py:180 templates/extras/configrevision.html:179 +msgid "GraphQL enabled" +msgstr "" + +#: netbox/config/parameters.py:182 +msgid "Enable the GraphQL API" +msgstr "" + +#: netbox/config/parameters.py:187 templates/extras/configrevision.html:183 +msgid "Changelog retention" +msgstr "" + +#: netbox/config/parameters.py:189 +msgid "Days to retain changelog history (set to zero for unlimited)" +msgstr "" + +#: netbox/config/parameters.py:194 +msgid "Job result retention" +msgstr "" + +#: netbox/config/parameters.py:196 +msgid "Days to retain job result history (set to zero for unlimited)" +msgstr "" + +#: netbox/config/parameters.py:201 templates/extras/configrevision.html:191 +msgid "Maps URL" +msgstr "" + +#: netbox/config/parameters.py:203 +msgid "Base URL for mapping geographic locations" +msgstr "" + +#: netbox/forms/__init__.py:13 +msgid "Partial match" +msgstr "" + +#: netbox/forms/__init__.py:14 +msgid "Exact match" +msgstr "" + +#: netbox/forms/__init__.py:15 +msgid "Starts with" +msgstr "" + +#: netbox/forms/__init__.py:16 +msgid "Ends with" +msgstr "" + +#: netbox/forms/__init__.py:17 +msgid "Regex" +msgstr "" + +#: netbox/forms/__init__.py:35 +msgid "Object type(s)" +msgstr "" + +#: netbox/forms/base.py:66 +msgid "Id" +msgstr "" + +#: netbox/forms/base.py:107 +msgid "Add tags" +msgstr "" + +#: netbox/forms/base.py:112 +msgid "Remove tags" +msgstr "" + +#: netbox/models/features.py:422 +msgid "Remote data source" +msgstr "" + +#: netbox/models/features.py:432 +msgid "data path" +msgstr "" + +#: netbox/models/features.py:436 +msgid "Path to remote file (relative to data source root)" +msgstr "" + +#: netbox/models/features.py:439 +msgid "auto sync enabled" +msgstr "" + +#: netbox/models/features.py:441 +msgid "Enable automatic synchronization of data when the data file is updated" +msgstr "" + +#: netbox/models/features.py:444 +msgid "date synced" +msgstr "" + +#: netbox/navigation/menu.py:12 +msgid "Organization" +msgstr "" + +#: netbox/navigation/menu.py:20 +msgid "Site Groups" +msgstr "" + +#: netbox/navigation/menu.py:28 +msgid "Rack Roles" +msgstr "" + +#: netbox/navigation/menu.py:32 +msgid "Elevations" +msgstr "" + +#: netbox/navigation/menu.py:41 +msgid "Tenant Groups" +msgstr "" + +#: netbox/navigation/menu.py:48 +msgid "Contact Groups" +msgstr "" + +#: netbox/navigation/menu.py:49 templates/tenancy/contactrole.html:8 +msgid "Contact Roles" +msgstr "" + +#: netbox/navigation/menu.py:50 +msgid "Contact Assignments" +msgstr "" + +#: netbox/navigation/menu.py:64 +msgid "Modules" +msgstr "" + +#: netbox/navigation/menu.py:65 templates/dcim/devicerole.html:8 +msgid "Device Roles" +msgstr "" + +#: netbox/navigation/menu.py:68 templates/dcim/device.html:179 +#: templates/dcim/virtualdevicecontext.html:8 +msgid "Virtual Device Contexts" +msgstr "" + +#: netbox/navigation/menu.py:76 +msgid "Manufacturers" +msgstr "" + +#: netbox/navigation/menu.py:80 +msgid "Device Components" +msgstr "" + +#: netbox/navigation/menu.py:92 templates/dcim/inventoryitemrole.html:8 +msgid "Inventory Item Roles" +msgstr "" + +#: netbox/navigation/menu.py:99 netbox/navigation/menu.py:103 +msgid "Connections" +msgstr "" + +#: netbox/navigation/menu.py:105 +msgid "Cables" +msgstr "" + +#: netbox/navigation/menu.py:106 +msgid "Wireless Links" +msgstr "" + +#: netbox/navigation/menu.py:109 +msgid "Interface Connections" +msgstr "" + +#: netbox/navigation/menu.py:114 +msgid "Console Connections" +msgstr "" + +#: netbox/navigation/menu.py:119 +msgid "Power Connections" +msgstr "" + +#: netbox/navigation/menu.py:135 +msgid "Wireless LAN Groups" +msgstr "" + +#: netbox/navigation/menu.py:156 +msgid "Prefix & VLAN Roles" +msgstr "" + +#: netbox/navigation/menu.py:162 +msgid "ASN Ranges" +msgstr "" + +#: netbox/navigation/menu.py:184 +msgid "VLAN Groups" +msgstr "" + +#: netbox/navigation/menu.py:191 +msgid "Service Templates" +msgstr "" + +#: netbox/navigation/menu.py:192 templates/dcim/device.html:321 +#: templates/ipam/ipaddress.html:122 +#: templates/virtualization/virtualmachine.html:155 +msgid "Services" +msgstr "" + +#: netbox/navigation/menu.py:199 +msgid "Overlay" +msgstr "" + +#: netbox/navigation/menu.py:206 templates/ipam/l2vpn.html:57 +msgid "Terminations" +msgstr "" + +#: netbox/navigation/menu.py:213 templates/dcim/device_edit.html:78 +msgid "Virtualization" +msgstr "" + +#: netbox/navigation/menu.py:217 netbox/navigation/menu.py:219 +#: virtualization/views.py:186 +msgid "Virtual Machines" +msgstr "" + +#: netbox/navigation/menu.py:227 +msgid "Cluster Types" +msgstr "" + +#: netbox/navigation/menu.py:228 +msgid "Cluster Groups" +msgstr "" + +#: netbox/navigation/menu.py:242 +msgid "Circuit Types" +msgstr "" + +#: netbox/navigation/menu.py:246 netbox/navigation/menu.py:248 +msgid "Providers" +msgstr "" + +#: netbox/navigation/menu.py:249 templates/circuits/provider.html:53 +msgid "Provider Accounts" +msgstr "" + +#: netbox/navigation/menu.py:250 +msgid "Provider Networks" +msgstr "" + +#: netbox/navigation/menu.py:264 +msgid "Power Panels" +msgstr "" + +#: netbox/navigation/menu.py:275 +msgid "Configurations" +msgstr "" + +#: netbox/navigation/menu.py:277 +msgid "Config Contexts" +msgstr "" + +#: netbox/navigation/menu.py:278 +msgid "Config Templates" +msgstr "" + +#: netbox/navigation/menu.py:285 netbox/navigation/menu.py:289 +msgid "Customization" +msgstr "" + +#: netbox/navigation/menu.py:291 +#: templates/circuits/circuittermination_edit.html:53 +#: templates/dcim/cable_edit.html:77 templates/dcim/device_edit.html:103 +#: templates/dcim/inventoryitem_edit.html:102 templates/dcim/rack_edit.html:81 +#: templates/dcim/virtualchassis_add.html:31 +#: templates/dcim/virtualchassis_edit.html:41 +#: templates/generic/bulk_edit.html:92 templates/htmx/form.html:32 +#: templates/inc/panels/custom_fields.html:7 +#: templates/ipam/ipaddress_bulk_add.html:35 +#: templates/ipam/ipaddress_edit.html:88 +#: templates/ipam/l2vpntermination_edit.html:51 +#: templates/ipam/service_create.html:75 templates/ipam/service_edit.html:62 +#: templates/ipam/vlan_edit.html:63 +msgid "Custom Fields" +msgstr "" + +#: netbox/navigation/menu.py:292 +msgid "Custom Field Choices" +msgstr "" + +#: netbox/navigation/menu.py:293 +msgid "Custom Links" +msgstr "" + +#: netbox/navigation/menu.py:294 +msgid "Export Templates" +msgstr "" + +#: netbox/navigation/menu.py:295 +msgid "Saved Filters" +msgstr "" + +#: netbox/navigation/menu.py:297 +msgid "Image Attachments" +msgstr "" + +#: netbox/navigation/menu.py:301 +msgid "Reports & Scripts" +msgstr "" + +#: netbox/navigation/menu.py:321 +msgid "Operations" +msgstr "" + +#: netbox/navigation/menu.py:325 +msgid "Integrations" +msgstr "" + +#: netbox/navigation/menu.py:327 +msgid "Data Sources" +msgstr "" + +#: netbox/navigation/menu.py:328 +msgid "Webhooks" +msgstr "" + +#: netbox/navigation/menu.py:332 netbox/navigation/menu.py:336 +#: netbox/views/generic/feature_views.py:151 +#: templates/extras/report/base.html:37 templates/extras/script/base.html:36 +msgid "Jobs" +msgstr "" + +#: netbox/navigation/menu.py:342 +msgid "Logging" +msgstr "" + +#: netbox/navigation/menu.py:344 +msgid "Journal Entries" +msgstr "" + +#: netbox/navigation/menu.py:345 templates/extras/objectchange.html:8 +#: templates/extras/objectchange_list.html:4 +msgid "Change Log" +msgstr "" + +#: netbox/navigation/menu.py:352 templates/inc/profile_button.html:18 +msgid "Admin" +msgstr "" + +#: netbox/navigation/menu.py:361 templates/users/group.html:27 +#: users/forms/model_forms.py:242 users/forms/model_forms.py:255 +#: users/forms/model_forms.py:309 users/tables.py:105 +msgid "Users" +msgstr "" + +#: netbox/navigation/menu.py:384 users/forms/model_forms.py:182 +#: users/forms/model_forms.py:195 users/forms/model_forms.py:314 +#: users/tables.py:35 users/tables.py:109 +msgid "Groups" +msgstr "" + +#: netbox/navigation/menu.py:406 templates/account/base.html:21 +#: templates/inc/profile_button.html:39 +msgid "API Tokens" +msgstr "" + +#: netbox/navigation/menu.py:413 users/forms/model_forms.py:188 +#: users/forms/model_forms.py:197 users/forms/model_forms.py:248 +#: users/forms/model_forms.py:256 +msgid "Permissions" +msgstr "" + +#: netbox/navigation/menu.py:425 +msgid "Current Config" +msgstr "" + +#: netbox/navigation/menu.py:431 +msgid "Config Revisions" +msgstr "" + +#: netbox/navigation/menu.py:471 templates/500.html:35 +#: templates/account/preferences.html:29 +msgid "Plugins" +msgstr "" + +#: netbox/preferences.py:17 +msgid "Color mode" +msgstr "" + +#: netbox/preferences.py:25 +msgid "Page length" +msgstr "" + +#: netbox/preferences.py:27 +msgid "The default number of objects to display per page" +msgstr "" + +#: netbox/preferences.py:31 +msgid "Paginator placement" +msgstr "" + +#: netbox/preferences.py:37 +msgid "Where the paginator controls will be displayed relative to a table" +msgstr "" + +#: netbox/preferences.py:43 +msgid "Data format" +msgstr "" + +#: netbox/tables/columns.py:175 +msgid "Toggle all" +msgstr "" + +#: netbox/tables/columns.py:277 templates/inc/profile_button.html:56 +msgid "Toggle Dropdown" +msgstr "" + +#: netbox/tables/columns.py:542 +msgid "Error" +msgstr "" + +#: netbox/tables/tables.py:234 templates/generic/bulk_import.html:115 +msgid "Field" +msgstr "" + +#: netbox/tables/tables.py:237 +msgid "Value" +msgstr "" + +#: netbox/tables/tables.py:246 +msgid "No results found" +msgstr "" + +#: netbox/tests/dummy_plugin/navigation.py:29 +msgid "Dummy Plugin" +msgstr "" + +#: netbox/views/generic/feature_views.py:38 +msgid "Changelog" +msgstr "" + +#: netbox/views/generic/feature_views.py:91 +msgid "Journal" +msgstr "" + +#: templates/403.html:4 +msgid "Access Denied" +msgstr "" + +#: templates/403.html:9 +msgid "You do not have permission to access this page" +msgstr "" + +#: templates/404.html:4 +msgid "Page Not Found" +msgstr "" + +#: templates/404.html:9 +msgid "The requested page does not exist" +msgstr "" + +#: templates/500.html:7 templates/500.html:18 +msgid "Server Error" +msgstr "" + +#: templates/500.html:23 +msgid "There was a problem with your request. Please contact an administrator" +msgstr "" + +#: templates/500.html:28 +msgid "The complete exception is provided below" +msgstr "" + +#: templates/500.html:33 +msgid "Python version" +msgstr "" + +#: templates/500.html:34 +msgid "NetBox version" +msgstr "" + +#: templates/500.html:36 +msgid "None installed" +msgstr "" + +#: templates/500.html:39 +msgid "If further assistance is required, please post to the" +msgstr "" + +#: templates/500.html:39 +msgid "NetBox discussion forum" +msgstr "" + +#: templates/500.html:39 +msgid "on GitHub" +msgstr "" + +#: templates/500.html:42 templates/base/40x.html:17 +msgid "Home Page" +msgstr "" + +#: templates/account/base.html:7 templates/inc/profile_button.html:24 +msgid "Profile" +msgstr "" + +#: templates/account/base.html:13 templates/inc/profile_button.html:34 +msgid "Preferences" +msgstr "" + +#: templates/account/password.html:5 +msgid "Change Password" +msgstr "" + +#: templates/account/password.html:17 templates/account/preferences.html:82 +#: templates/dcim/devicebay_populate.html:34 +#: templates/dcim/virtualchassis_add_member.html:24 +#: templates/dcim/virtualchassis_edit.html:104 +#: templates/extras/configrevision_restore.html:80 +#: templates/extras/object_journal.html:26 templates/extras/script.html:36 +#: templates/generic/bulk_add_component.html:55 +#: templates/generic/bulk_delete.html:46 templates/generic/bulk_edit.html:125 +#: templates/generic/bulk_import.html:53 templates/generic/bulk_import.html:75 +#: templates/generic/bulk_import.html:97 templates/generic/bulk_remove.html:42 +#: templates/generic/bulk_rename.html:44 +#: templates/generic/confirmation_form.html:20 +#: templates/generic/object_edit.html:76 templates/htmx/delete_form.html:19 +#: templates/htmx/delete_form.html:21 templates/ipam/ipaddress_assign.html:31 +#: templates/virtualization/cluster_add_devices.html:30 +msgid "Cancel" +msgstr "" + +#: templates/account/password.html:18 templates/account/preferences.html:83 +#: templates/dcim/devicebay_populate.html:35 +#: templates/dcim/virtualchassis_add_member.html:26 +#: templates/dcim/virtualchassis_edit.html:106 +#: templates/extras/dashboard/widget_add.html:26 +#: templates/extras/dashboard/widget_config.html:19 +#: templates/extras/object_journal.html:27 +#: templates/generic/object_edit.html:66 +#: utilities/templates/helpers/applied_filters.html:16 +#: utilities/templates/helpers/table_config_form.html:40 +msgid "Save" +msgstr "" + +#: templates/account/preferences.html:41 +msgid "Table Configurations" +msgstr "" + +#: templates/account/preferences.html:46 +msgid "Clear table preferences" +msgstr "" + +#: templates/account/preferences.html:53 +msgid "Toggle All" +msgstr "" + +#: templates/account/preferences.html:55 +msgid "Table" +msgstr "" + +#: templates/account/preferences.html:56 +msgid "Ordering" +msgstr "" + +#: templates/account/preferences.html:57 +msgid "Columns" +msgstr "" + +#: templates/account/preferences.html:76 templates/dcim/cable_trace.html:113 +#: templates/extras/object_configcontext.html:55 +msgid "None found" +msgstr "" + +#: templates/account/profile.html:6 +msgid "User Profile" +msgstr "" + +#: templates/account/profile.html:12 +msgid "Account Details" +msgstr "" + +#: templates/account/profile.html:30 templates/tenancy/contact.html:44 +#: templates/users/user.html:26 tenancy/forms/bulk_edit.py:108 +msgid "Email" +msgstr "" + +#: templates/account/profile.html:34 templates/users/user.html:30 +msgid "Account Created" +msgstr "" + +#: templates/account/profile.html:38 templates/users/user.html:42 +msgid "Superuser" +msgstr "" + +#: templates/account/profile.html:42 +msgid "Admin Access" +msgstr "" + +#: templates/account/profile.html:51 templates/users/objectpermission.html:86 +#: templates/users/user.html:51 +msgid "Assigned Groups" +msgstr "" + +#: templates/account/profile.html:56 +#: templates/circuits/circuit_terminations_swap.html:18 +#: templates/circuits/circuit_terminations_swap.html:26 +#: templates/circuits/inc/circuit_termination.html:154 +#: templates/dcim/devicebay.html:66 +#: templates/dcim/inc/panels/inventory_items.html:37 +#: templates/dcim/interface.html:302 templates/dcim/modulebay.html:79 +#: templates/extras/configcontext.html:73 +#: templates/extras/htmx/script_result.html:54 +#: templates/extras/object_configcontext.html:28 +#: templates/extras/objectchange.html:128 +#: templates/extras/objectchange.html:145 templates/extras/webhook.html:122 +#: templates/extras/webhook.html:134 templates/extras/webhook.html:146 +#: templates/inc/panel_table.html:12 templates/inc/panels/comments.html:12 +#: templates/ipam/inc/panels/fhrp_groups.html:43 templates/users/group.html:32 +#: templates/users/group.html:42 templates/users/objectpermission.html:81 +#: templates/users/objectpermission.html:91 templates/users/user.html:56 +#: templates/users/user.html:66 +msgid "None" +msgstr "" + +#: templates/account/profile.html:66 templates/users/user.html:76 +msgid "Recent Activity" +msgstr "" + +#: templates/account/token.html:8 templates/account/token_list.html:6 +msgid "My API Tokens" +msgstr "" + +#: templates/account/token.html:11 templates/account/token.html:19 +#: templates/users/token.html:6 templates/users/token.html:14 +#: users/forms/filtersets.py:123 +msgid "Token" +msgstr "" + +#: templates/account/token.html:40 templates/users/token.html:32 +#: users/forms/bulk_edit.py:87 +msgid "Write enabled" +msgstr "" + +#: templates/account/token.html:52 templates/users/token.html:44 +msgid "Last used" +msgstr "" + +#: templates/account/token_list.html:12 +msgid "Add a Token" +msgstr "" + +#: templates/admin/index.html:10 +msgid "System" +msgstr "" + +#: templates/admin/index.html:14 +msgid "Background Tasks" +msgstr "" + +#: templates/admin/index.html:19 +msgid "Installed plugins" +msgstr "" + +#: templates/base/base.html:28 templates/extras/admin/plugins_list.html:8 +#: templates/home.html:24 +msgid "Home" +msgstr "" + +#: templates/base/layout.html:27 templates/base/layout.html:37 +#: templates/login.html:34 +msgid "NetBox logo" +msgstr "" + +#: templates/base/layout.html:76 +msgid "Debug mode is enabled" +msgstr "" + +#: templates/base/layout.html:77 +msgid "" +"Performance may be limited. Debugging should never be enabled on a " +"production system" +msgstr "" + +#: templates/base/layout.html:83 +msgid "Maintenance Mode" +msgstr "" + +#: templates/base/layout.html:134 +msgid "Docs" +msgstr "" + +#: templates/base/layout.html:139 templates/rest_framework/api.html:10 +msgid "REST API" +msgstr "" + +#: templates/base/layout.html:144 +msgid "REST API documentation" +msgstr "" + +#: templates/base/layout.html:150 +msgid "GraphQL API" +msgstr "" + +#: templates/base/layout.html:156 +msgid "Source Code" +msgstr "" + +#: templates/base/layout.html:161 +msgid "Community" +msgstr "" + +#: templates/base/sidenav.html:12 templates/base/sidenav.html:17 +msgid "NetBox Logo" +msgstr "" + +#: templates/circuits/circuit.html:48 +msgid "Install Date" +msgstr "" + +#: templates/circuits/circuit.html:52 +msgid "Termination Date" +msgstr "" + +#: templates/circuits/circuit_terminations_swap.html:4 +msgid "Swap Circuit Terminations" +msgstr "" + +#: templates/circuits/circuit_terminations_swap.html:8 +#, python-format +msgid "Swap these terminations for circuit %(circuit)s?" +msgstr "" + +#: templates/circuits/circuit_terminations_swap.html:14 +msgid "A side" +msgstr "" + +#: templates/circuits/circuit_terminations_swap.html:22 +msgid "Z side" +msgstr "" + +#: templates/circuits/circuittermination_edit.html:9 +#: templates/circuits/inc/circuit_termination.html:81 +#: templates/dcim/frontport.html:128 templates/dcim/interface.html:195 +#: templates/dcim/rearport.html:118 +msgid "Circuit Termination" +msgstr "" + +#: templates/circuits/circuittermination_edit.html:41 +msgid "Termination Details" +msgstr "" + +#: templates/circuits/circuittype.html:10 +msgid "Add Circuit" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:9 +#: templates/dcim/devicetype/component_templates.html:30 +#: templates/dcim/manufacturer.html:11 +#: templates/dcim/moduletype/component_templates.html:30 +#: templates/generic/bulk_add_component.html:8 +#: templates/users/objectpermission.html:41 +#: utilities/templates/buttons/add.html:4 +#: utilities/templates/helpers/table_config_form.html:20 +msgid "Add" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:14 +#: templates/circuits/inc/circuit_termination.html:63 +#: templates/dcim/devicetype/component_templates.html:21 +#: templates/dcim/inc/panels/inventory_items.html:24 +#: templates/dcim/moduletype/component_templates.html:21 +#: templates/dcim/powerpanel.html:61 templates/generic/object_edit.html:29 +#: templates/ipam/inc/ipaddress_edit_header.html:10 +#: templates/ipam/inc/panels/fhrp_groups.html:30 +#: utilities/templates/buttons/edit.html:3 +msgid "Edit" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:17 +msgid "Swap" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:26 +#, python-format +msgid "Termination %(side)s" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:42 +#: templates/dcim/cable.html:70 templates/dcim/cable.html:76 +msgid "Termination" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:46 +#: templates/dcim/consoleport.html:62 templates/dcim/consoleserverport.html:62 +#: templates/dcim/powerfeed.html:122 +msgid "Marked as connected" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:48 +msgid "to" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:58 +#: templates/circuits/inc/circuit_termination.html:59 +#: templates/dcim/frontport.html:87 +#: templates/dcim/inc/connection_endpoints.html:7 +#: templates/dcim/interface.html:156 templates/dcim/rearport.html:83 +msgid "Trace" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:62 +msgid "Edit cable" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:67 +msgid "Remove cable" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:68 +#: templates/dcim/bulk_disconnect.html:5 +#: templates/dcim/device/consoleports.html:12 +#: templates/dcim/device/consoleserverports.html:12 +#: templates/dcim/device/frontports.html:12 +#: templates/dcim/device/interfaces.html:16 +#: templates/dcim/device/poweroutlets.html:12 +#: templates/dcim/device/powerports.html:12 +#: templates/dcim/device/rearports.html:12 templates/dcim/powerpanel.html:66 +msgid "Disconnect" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:75 +#: templates/dcim/consoleport.html:71 templates/dcim/consoleserverport.html:71 +#: templates/dcim/frontport.html:109 templates/dcim/interface.html:182 +#: templates/dcim/interface.html:202 templates/dcim/powerfeed.html:136 +#: templates/dcim/poweroutlet.html:75 templates/dcim/poweroutlet.html:76 +#: templates/dcim/powerport.html:77 templates/dcim/rearport.html:105 +msgid "Connect" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:79 +#: templates/dcim/consoleport.html:78 templates/dcim/consoleserverport.html:78 +#: templates/dcim/frontport.html:18 templates/dcim/frontport.html:122 +#: templates/dcim/interface.html:189 templates/dcim/inventoryitem_edit.html:49 +#: templates/dcim/rearport.html:112 +msgid "Front Port" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:97 +msgid "Downstream" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:98 +msgid "Upstream" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:107 +msgid "Cross-Connect" +msgstr "" + +#: templates/circuits/inc/circuit_termination.html:111 +msgid "Patch Panel/Port" +msgstr "" + +#: templates/circuits/provider.html:11 +msgid "Add circuit" +msgstr "" + +#: templates/circuits/provideraccount.html:17 +msgid "Provider Account" +msgstr "" + +#: templates/core/datafile.html:47 +msgid "Last Updated" +msgstr "" + +#: templates/core/datafile.html:51 templates/ipam/iprange.html:28 +msgid "Size" +msgstr "" + +#: templates/core/datafile.html:52 +msgid "bytes" +msgstr "" + +#: templates/core/datafile.html:55 +msgid "SHA256 Hash" +msgstr "" + +#: templates/core/datasource.html:14 templates/core/datasource.html:20 +#: utilities/templates/buttons/sync.html:5 +msgid "Sync" +msgstr "" + +#: templates/core/datasource.html:51 +msgid "Last synced" +msgstr "" + +#: templates/core/datasource.html:86 +msgid "Backend" +msgstr "" + +#: templates/core/datasource.html:102 +msgid "No parameters defined" +msgstr "" + +#: templates/core/datasource.html:118 +msgid "Files" +msgstr "" + +#: templates/core/job.html:21 +msgid "Job" +msgstr "" + +#: templates/core/job.html:39 templates/extras/journalentry.html:29 +msgid "Created By" +msgstr "" + +#: templates/core/job.html:48 +msgid "Scheduling" +msgstr "" + +#: templates/core/job.html:60 +#, python-format +msgid "every %(interval)s seconds" +msgstr "" + +#: templates/dcim/bulk_disconnect.html:9 +#, python-format +msgid "" +"Are you sure you want to disconnect these %(count)s %(obj_type_plural)s?" +msgstr "" + +#: templates/dcim/cable_edit.html:12 +msgid "A Side" +msgstr "" + +#: templates/dcim/cable_edit.html:29 +msgid "B Side" +msgstr "" + +#: templates/dcim/cable_trace.html:6 +#, python-format +msgid "Cable Trace for %(object_type)s %(object)s" +msgstr "" + +#: templates/dcim/cable_trace.html:21 templates/dcim/inc/rack_elevation.html:7 +msgid "Download SVG" +msgstr "" + +#: templates/dcim/cable_trace.html:27 +msgid "Asymmetric Path" +msgstr "" + +#: templates/dcim/cable_trace.html:28 +msgid "The nodes below have no links and result in an asymmetric path" +msgstr "" + +#: templates/dcim/cable_trace.html:35 +msgid "Path split" +msgstr "" + +#: templates/dcim/cable_trace.html:36 +msgid "Select a node below to continue" +msgstr "" + +#: templates/dcim/cable_trace.html:52 +msgid "Trace Completed" +msgstr "" + +#: templates/dcim/cable_trace.html:55 +msgid "Total segments" +msgstr "" + +#: templates/dcim/cable_trace.html:59 +msgid "Total length" +msgstr "" + +#: templates/dcim/cable_trace.html:74 +msgid "No paths found" +msgstr "" + +#: templates/dcim/cable_trace.html:83 +msgid "Related Paths" +msgstr "" + +#: templates/dcim/cable_trace.html:89 +msgid "Origin" +msgstr "" + +#: templates/dcim/cable_trace.html:90 +msgid "Destination" +msgstr "" + +#: templates/dcim/cable_trace.html:91 +msgid "Segments" +msgstr "" + +#: templates/dcim/cable_trace.html:104 +msgid "Incomplete" +msgstr "" + +#: templates/dcim/component_list.html:14 +msgid "Rename Selected" +msgstr "" + +#: templates/dcim/consoleport.html:67 templates/dcim/consoleserverport.html:67 +#: templates/dcim/frontport.html:105 templates/dcim/interface.html:178 +#: templates/dcim/poweroutlet.html:73 templates/dcim/powerport.html:73 +msgid "Not Connected" +msgstr "" + +#: templates/dcim/consoleport.html:75 templates/dcim/consoleserverport.html:18 +#: templates/dcim/frontport.html:116 templates/dcim/inventoryitem_edit.html:44 +msgid "Console Server Port" +msgstr "" + +#: templates/dcim/device.html:52 +msgid "Highlight device" +msgstr "" + +#: templates/dcim/device.html:74 +msgid "Not racked" +msgstr "" + +#: templates/dcim/device.html:81 templates/dcim/site.html:109 +msgid "GPS Coordinates" +msgstr "" + +#: templates/dcim/device.html:87 templates/dcim/site.html:115 +msgid "Map It" +msgstr "" + +#: templates/dcim/device.html:127 templates/dcim/inventoryitem.html:57 +#: templates/dcim/module.html:79 templates/dcim/modulebay.html:73 +#: templates/dcim/rack.html:69 +msgid "Asset Tag" +msgstr "" + +#: templates/dcim/device.html:170 +msgid "View Virtual Chassis" +msgstr "" + +#: templates/dcim/device.html:187 +msgid "Create VDC" +msgstr "" + +#: templates/dcim/device.html:196 templates/dcim/device_edit.html:64 +#: virtualization/forms/model_forms.py:224 +msgid "Management" +msgstr "" + +#: templates/dcim/device.html:217 templates/dcim/device.html:233 +#: templates/virtualization/virtualmachine.html:56 +#: templates/virtualization/virtualmachine.html:72 +msgid "NAT for" +msgstr "" + +#: templates/dcim/device.html:219 templates/dcim/device.html:235 +#: templates/virtualization/virtualmachine.html:58 +#: templates/virtualization/virtualmachine.html:74 +msgid "NAT" +msgstr "" + +#: templates/dcim/device.html:271 templates/dcim/rack.html:77 +msgid "Power Utilization" +msgstr "" + +#: templates/dcim/device.html:276 +msgid "Input" +msgstr "" + +#: templates/dcim/device.html:277 +msgid "Outlets" +msgstr "" + +#: templates/dcim/device.html:278 +msgid "Allocated" +msgstr "" + +#: templates/dcim/device.html:287 templates/dcim/device.html:289 +#: templates/dcim/device.html:305 templates/dcim/powerfeed.html:70 +msgid "VA" +msgstr "" + +#: templates/dcim/device.html:299 +msgctxt "Leg of a power feed" +msgid "Leg" +msgstr "" + +#: templates/dcim/device.html:329 +#: templates/virtualization/virtualmachine.html:163 +msgid "Add a service" +msgstr "" + +#: templates/dcim/device.html:336 templates/dcim/rack.html:84 +#: templates/dcim/rack_edit.html:38 +msgid "Dimensions" +msgstr "" + +#: templates/dcim/device/base.html:21 templates/dcim/device_list.html:9 +#: templates/dcim/devicetype/base.html:18 templates/dcim/module.html:18 +#: templates/dcim/moduletype/base.html:18 +#: templates/virtualization/virtualmachine_list.html:8 +msgid "Add Components" +msgstr "" + +#: templates/dcim/device/consoleports.html:24 +msgid "Add Console Ports" +msgstr "" + +#: templates/dcim/device/consoleserverports.html:24 +msgid "Add Console Server Ports" +msgstr "" + +#: templates/dcim/device/devicebays.html:10 +msgid "Add Device Bays" +msgstr "" + +#: templates/dcim/device/frontports.html:24 +msgid "Add Front Ports" +msgstr "" + +#: templates/dcim/device/inc/interface_table_controls.html:9 +msgid "Hide Enabled" +msgstr "" + +#: templates/dcim/device/inc/interface_table_controls.html:10 +msgid "Hide Disabled" +msgstr "" + +#: templates/dcim/device/inc/interface_table_controls.html:11 +msgid "Hide Virtual" +msgstr "" + +#: templates/dcim/device/inc/interface_table_controls.html:12 +msgid "Hide Disconnected" +msgstr "" + +#: templates/dcim/device/interfaces.html:28 +#: templates/virtualization/virtualmachine/base.html:21 +msgid "Add Interfaces" +msgstr "" + +#: templates/dcim/device/inventory.html:10 +#: templates/dcim/inc/panels/inventory_items.html:46 +msgid "Add Inventory Item" +msgstr "" + +#: templates/dcim/device/modulebays.html:10 +msgid "Add Module Bays" +msgstr "" + +#: templates/dcim/device/poweroutlets.html:24 +msgid "Add Power Outlets" +msgstr "" + +#: templates/dcim/device/powerports.html:24 +msgid "Add Power Port" +msgstr "" + +#: templates/dcim/device/rearports.html:24 +msgid "Add Rear Ports" +msgstr "" + +#: templates/dcim/device/render_config.html:5 +#: templates/virtualization/virtualmachine/render_config.html:5 +msgid "Config" +msgstr "" + +#: templates/dcim/device/render_config.html:37 +#: templates/virtualization/virtualmachine/render_config.html:37 +msgid "Context Data" +msgstr "" + +#: templates/dcim/device/render_config.html:57 +#: templates/virtualization/virtualmachine/render_config.html:57 +msgid "Download" +msgstr "" + +#: templates/dcim/device/render_config.html:60 +#: templates/virtualization/virtualmachine/render_config.html:60 +msgid "Rendered Config" +msgstr "" + +#: templates/dcim/device/render_config.html:65 +#: templates/virtualization/virtualmachine/render_config.html:65 +msgid "No configuration template found" +msgstr "" + +#: templates/dcim/device_edit.html:44 +msgid "Parent Bay" +msgstr "" + +#: templates/dcim/device_edit.html:48 +#: utilities/templates/form_helpers/render_field.html:20 +msgid "Regenerate Slug" +msgstr "" + +#: templates/dcim/device_edit.html:49 templates/generic/bulk_remove.html:7 +#: utilities/templates/helpers/table_config_form.html:23 +msgid "Remove" +msgstr "" + +#: templates/dcim/device_edit.html:110 +msgid "Local Config Context Data" +msgstr "" + +#: templates/dcim/device_list.html:82 +#: templates/dcim/devicetype/component_templates.html:18 +#: templates/dcim/moduletype/component_templates.html:18 +#: templates/generic/bulk_rename.html:34 +#: templates/virtualization/virtualmachine/interfaces.html:11 +msgid "Rename" +msgstr "" + +#: templates/dcim/devicebay.html:18 +msgid "Device Bay" +msgstr "" + +#: templates/dcim/devicebay.html:48 +msgid "Installed Device" +msgstr "" + +#: templates/dcim/devicebay_delete.html:6 +#, python-format +msgid "Delete device bay %(devicebay)s?" +msgstr "" + +#: templates/dcim/devicebay_delete.html:11 +#, python-format +msgid "" +"Are you sure you want to delete this device bay from %(device)s?" +msgstr "" + +#: templates/dcim/devicebay_depopulate.html:6 +#, python-format +msgid "Remove %(device)s from %(device_bay)s?" +msgstr "" + +#: templates/dcim/devicebay_depopulate.html:13 +#, python-format +msgid "" +"Are you sure you want to remove %(device)s from " +"%(device_bay)s?" +msgstr "" + +#: templates/dcim/devicebay_populate.html:13 +msgid "Populate" +msgstr "" + +#: templates/dcim/devicebay_populate.html:22 +msgid "Bay" +msgstr "" + +#: templates/dcim/devicerole.html:14 templates/dcim/platform.html:17 +msgid "Add Device" +msgstr "" + +#: templates/dcim/devicerole.html:43 +msgid "VM Role" +msgstr "" + +#: templates/dcim/devicetype.html:21 templates/dcim/moduletype.html:19 +msgid "Model Name" +msgstr "" + +#: templates/dcim/devicetype.html:28 templates/dcim/moduletype.html:23 +msgid "Part Number" +msgstr "" + +#: templates/dcim/devicetype.html:40 +msgid "Height (U" +msgstr "" + +#: templates/dcim/devicetype.html:44 +msgid "Exclude From Utilization" +msgstr "" + +#: templates/dcim/devicetype.html:62 +msgid "Parent/Child" +msgstr "" + +#: templates/dcim/devicetype.html:74 +msgid "Front Image" +msgstr "" + +#: templates/dcim/devicetype.html:86 +msgid "Rear Image" +msgstr "" + +#: templates/dcim/frontport.html:57 +msgid "Rear Port Position" +msgstr "" + +#: templates/dcim/frontport.html:79 templates/dcim/interface.html:146 +#: templates/dcim/poweroutlet.html:67 templates/dcim/powerport.html:67 +#: templates/dcim/rearport.html:75 +msgid "Marked as Connected" +msgstr "" + +#: templates/dcim/frontport.html:93 templates/dcim/rearport.html:89 +msgid "Connection Status" +msgstr "" + +#: templates/dcim/inc/cable_termination.html:65 +msgid "No termination" +msgstr "" + +#: templates/dcim/inc/cable_toggle_buttons.html:4 +msgid "Mark Planned" +msgstr "" + +#: templates/dcim/inc/cable_toggle_buttons.html:8 +msgid "Mark Installed" +msgstr "" + +#: templates/dcim/inc/connection_endpoints.html:13 +msgid "Path Status" +msgstr "" + +#: templates/dcim/inc/connection_endpoints.html:18 +msgid "Not Reachable" +msgstr "" + +#: templates/dcim/inc/connection_endpoints.html:23 +msgid "Path Endpoints" +msgstr "" + +#: templates/dcim/inc/endpoint_connection.html:8 +#: templates/dcim/powerfeed.html:128 templates/dcim/rearport.html:101 +msgid "Not connected" +msgstr "" + +#: templates/dcim/inc/interface_vlans_table.html:6 +msgid "Untagged" +msgstr "" + +#: templates/dcim/inc/interface_vlans_table.html:37 +msgid "No VLANs Assigned" +msgstr "" + +#: templates/dcim/inc/interface_vlans_table.html:44 +#: templates/ipam/prefix_list.html:16 templates/ipam/prefix_list.html:33 +msgid "Clear" +msgstr "" + +#: templates/dcim/inc/interface_vlans_table.html:47 +msgid "Clear All" +msgstr "" + +#: templates/dcim/interface.html:17 +msgid "Add Child Interface" +msgstr "" + +#: templates/dcim/interface.html:51 +msgid "Speed/Duplex" +msgstr "" + +#: templates/dcim/interface.html:74 +msgid "PoE Mode" +msgstr "" + +#: templates/dcim/interface.html:78 +msgid "PoE Type" +msgstr "" + +#: templates/dcim/interface.html:82 +#: templates/virtualization/vminterface.html:66 +msgid "802.1Q Mode" +msgstr "" + +#: templates/dcim/interface.html:126 +#: templates/virtualization/vminterface.html:62 +msgid "MAC Address" +msgstr "" + +#: templates/dcim/interface.html:153 +msgid "Wireless Link" +msgstr "" + +#: templates/dcim/interface.html:222 +msgid "Peer" +msgstr "" + +#: templates/dcim/interface.html:234 +#: templates/wireless/inc/wirelesslink_interface.html:26 +msgid "Channel" +msgstr "" + +#: templates/dcim/interface.html:243 +#: templates/wireless/inc/wirelesslink_interface.html:32 +msgid "Channel Frequency" +msgstr "" + +#: templates/dcim/interface.html:246 templates/dcim/interface.html:254 +#: templates/dcim/interface.html:265 templates/dcim/interface.html:273 +msgid "MHz" +msgstr "" + +#: templates/dcim/interface.html:262 +#: templates/wireless/inc/wirelesslink_interface.html:42 +msgid "Channel Width" +msgstr "" + +#: templates/dcim/interface.html:291 templates/wireless/wirelesslan.html:15 +#: templates/wireless/wirelesslink.html:24 wireless/forms/bulk_edit.py:59 +#: wireless/forms/bulk_edit.py:101 wireless/forms/filtersets.py:39 +#: wireless/forms/filtersets.py:79 wireless/models.py:81 wireless/models.py:155 +#: wireless/tables/wirelesslan.py:44 +msgid "SSID" +msgstr "" + +#: templates/dcim/interface.html:312 +msgid "LAG Members" +msgstr "" + +#: templates/dcim/interface.html:331 +msgid "No member interfaces" +msgstr "" + +#: templates/dcim/interface.html:355 templates/ipam/fhrpgroup.html:80 +#: templates/ipam/iprange/ip_addresses.html:7 +#: templates/ipam/prefix/ip_addresses.html:7 +#: templates/virtualization/vminterface.html:92 +msgid "Add IP Address" +msgstr "" + +#: templates/dcim/inventoryitem.html:25 +msgid "Parent Item" +msgstr "" + +#: templates/dcim/inventoryitem.html:49 +msgid "Part ID" +msgstr "" + +#: templates/dcim/inventoryitem_bulk_delete.html:5 +msgid "This will also delete all child inventory items of those listed" +msgstr "" + +#: templates/dcim/inventoryitem_edit.html:33 +msgid "Component Assignment" +msgstr "" + +#: templates/dcim/inventoryitem_edit.html:59 templates/dcim/poweroutlet.html:18 +#: templates/dcim/powerport.html:81 +msgid "Power Outlet" +msgstr "" + +#: templates/dcim/location.html:17 +msgid "Add Child Location" +msgstr "" + +#: templates/dcim/location.html:76 +msgid "Child Locations" +msgstr "" + +#: templates/dcim/location.html:84 templates/dcim/site.html:150 +msgid "Add a Location" +msgstr "" + +#: templates/dcim/location.html:98 templates/dcim/site.html:164 +msgid "Add a Device" +msgstr "" + +#: templates/dcim/manufacturer.html:16 +msgid "Add Device Type" +msgstr "" + +#: templates/dcim/manufacturer.html:21 +msgid "Add Module Type" +msgstr "" + +#: templates/dcim/powerfeed.html:56 +msgid "Connected Device" +msgstr "" + +#: templates/dcim/powerfeed.html:66 +msgid "Utilization (Allocated" +msgstr "" + +#: templates/dcim/powerfeed.html:85 +msgid "Electrical Characteristics" +msgstr "" + +#: templates/dcim/powerfeed.html:95 +msgctxt "Abbreviation for volts" +msgid "V" +msgstr "" + +#: templates/dcim/powerfeed.html:99 +msgctxt "Abbreviation for amperes" +msgid "A" +msgstr "" + +#: templates/dcim/poweroutlet.html:51 +msgid "Feed Leg" +msgstr "" + +#: templates/dcim/powerpanel.html:77 +msgid "Add Power Feeds" +msgstr "" + +#: templates/dcim/powerport.html:47 +msgid "Maximum Draw" +msgstr "" + +#: templates/dcim/powerport.html:51 +msgid "Allocated Draw" +msgstr "" + +#: templates/dcim/rack.html:73 +msgid "Space Utilization" +msgstr "" + +#: templates/dcim/rack.html:103 +msgid "descending" +msgstr "" + +#: templates/dcim/rack.html:103 +msgid "ascending" +msgstr "" + +#: templates/dcim/rack.html:106 +msgid "Starting Unit" +msgstr "" + +#: templates/dcim/rack.html:132 +msgid "Mounting Depth" +msgstr "" + +#: templates/dcim/rack.html:142 +msgid "Rack Weight" +msgstr "" + +#: templates/dcim/rack.html:152 templates/dcim/rack_edit.html:67 +msgid "Maximum Weight" +msgstr "" + +#: templates/dcim/rack.html:162 +msgid "Total Weight" +msgstr "" + +#: templates/dcim/rack.html:180 templates/dcim/rack_elevation_list.html:16 +msgid "Images and Labels" +msgstr "" + +#: templates/dcim/rack.html:181 templates/dcim/rack_elevation_list.html:17 +msgid "Images only" +msgstr "" + +#: templates/dcim/rack.html:182 templates/dcim/rack_elevation_list.html:18 +msgid "Labels only" +msgstr "" + +#: templates/dcim/rack/reservations.html:9 +msgid "Add reservation" +msgstr "" + +#: templates/dcim/rack_edit.html:21 +msgid "Inventory Control" +msgstr "" + +#: templates/dcim/rack_edit.html:45 +msgid "Outer Dimensions" +msgstr "" + +#: templates/dcim/rack_edit.html:56 templates/dcim/rack_edit.html:71 +msgid "Unit" +msgstr "" + +#: templates/dcim/rack_elevation_list.html:12 +msgid "View List" +msgstr "" + +#: templates/dcim/rack_elevation_list.html:27 +msgid "Sort By" +msgstr "" + +#: templates/dcim/rack_elevation_list.html:77 +msgid "No Racks Found" +msgstr "" + +#: templates/dcim/rack_list.html:8 +msgid "View Elevations" +msgstr "" + +#: templates/dcim/rackreservation.html:47 +msgid "Reservation Details" +msgstr "" + +#: templates/dcim/rackrole.html:10 +msgid "Add Rack" +msgstr "" + +#: templates/dcim/rearport.html:53 +msgid "Positions" +msgstr "" + +#: templates/dcim/region.html:17 templates/dcim/sitegroup.html:17 +msgid "Add Site" +msgstr "" + +#: templates/dcim/region.html:56 +msgid "Child Regions" +msgstr "" + +#: templates/dcim/region.html:64 +msgid "Add Region" +msgstr "" + +#: templates/dcim/site.html:69 +msgid "Facility" +msgstr "" + +#: templates/dcim/site.html:77 +msgid "Time Zone" +msgstr "" + +#: templates/dcim/site.html:80 +msgid "UTC" +msgstr "" + +#: templates/dcim/site.html:81 +msgid "Site time" +msgstr "" + +#: templates/dcim/site.html:88 +msgid "Physical Address" +msgstr "" + +#: templates/dcim/site.html:94 +msgid "Map" +msgstr "" + +#: templates/dcim/site.html:105 +msgid "Shipping Address" +msgstr "" + +#: templates/dcim/sitegroup.html:56 templates/tenancy/contactgroup.html:49 +#: templates/tenancy/tenantgroup.html:58 +#: templates/wireless/wirelesslangroup.html:56 +msgid "Child Groups" +msgstr "" + +#: templates/dcim/sitegroup.html:64 +msgid "Add Site Group" +msgstr "" + +#: templates/dcim/trace/attachment.html:5 +#: templates/extras/exporttemplate.html:37 +msgid "Attachment" +msgstr "" + +#: templates/dcim/virtualchassis.html:86 +msgid "Add Member" +msgstr "" + +#: templates/dcim/virtualchassis_add.html:18 +msgid "Member Devices" +msgstr "" + +#: templates/dcim/virtualchassis_add_member.html:6 +#, python-format +msgid "Add New Member to Virtual Chassis %(virtual_chassis)s" +msgstr "" + +#: templates/dcim/virtualchassis_add_member.html:17 +msgid "Add New Member" +msgstr "" + +#: templates/dcim/virtualchassis_add_member.html:25 +msgid "Add Another" +msgstr "" + +#: templates/dcim/virtualchassis_edit.html:7 +#, python-format +msgid "Editing Virtual Chassis %(name)s" +msgstr "" + +#: templates/dcim/virtualchassis_edit.html:54 +msgid "Rack/Unit" +msgstr "" + +#: templates/dcim/virtualchassis_remove_member.html:5 +msgid "Remove Virtual Chassis Member" +msgstr "" + +#: templates/dcim/virtualchassis_remove_member.html:9 +#, python-format +msgid "" +"Are you sure you want to remove %(device)s from virtual " +"chassis %(name)s?" +msgstr "" + +#: templates/dcim/virtualdevicecontext.html:29 templates/ipam/l2vpn.html:19 +msgid "Identifier" +msgstr "" + +#: templates/exceptions/import_error.html:6 +msgid "" +"A module import error occurred during this request. Common causes include " +"the following:" +msgstr "" + +#: templates/exceptions/import_error.html:10 +msgid "Missing required packages" +msgstr "" + +#: templates/exceptions/import_error.html:11 +msgid "" +"This installation of NetBox might be missing one or more required Python " +"packages. These packages are listed in requirements.txt and " +"local_requirements.txt, and are normally installed as part of " +"the installation or upgrade process. To verify installed packages, run " +"pip freeze from the console and compare the output to the list " +"of required packages." +msgstr "" + +#: templates/exceptions/import_error.html:20 +msgid "WSGI service not restarted after upgrade" +msgstr "" + +#: templates/exceptions/import_error.html:21 +msgid "" +"If this installation has recently been upgraded, check that the WSGI service " +"(e.g. gunicorn or uWSGI) has been restarted. This ensures that the new code " +"is running." +msgstr "" + +#: templates/exceptions/permission_error.html:6 +msgid "" +"A file permission error was detected while processing this request. Common " +"causes include the following:" +msgstr "" + +#: templates/exceptions/permission_error.html:10 +msgid "Insufficient write permission to the media root" +msgstr "" + +#: templates/exceptions/permission_error.html:11 +#, python-format +msgid "" +"The configured media root is %(media_root)s. Ensure that the " +"user NetBox runs as has access to write files to all locations within this " +"path." +msgstr "" + +#: templates/exceptions/programming_error.html:6 +msgid "" +"A database programming error was detected while processing this request. " +"Common causes include the following:" +msgstr "" + +#: templates/exceptions/programming_error.html:10 +msgid "Database migrations missing" +msgstr "" + +#: templates/exceptions/programming_error.html:11 +msgid "" +"When upgrading to a new NetBox release, the upgrade script must be run to " +"apply any new database migrations. You can run migrations manually by " +"executing python3 manage.py migrate from the command line." +msgstr "" + +#: templates/exceptions/programming_error.html:18 +msgid "Unsupported PostgreSQL version" +msgstr "" + +#: templates/exceptions/programming_error.html:19 +msgid "" +"Ensure that PostgreSQL version 12 or later is in use. You can check this by " +"connecting to the database using NetBox's credentials and issuing a query " +"for SELECT VERSION()." +msgstr "" + +#: templates/extras/admin/plugins_list.html:4 +#: templates/extras/admin/plugins_list.html:9 +#: templates/extras/admin/plugins_list.html:13 +msgid "Installed Plugins" +msgstr "" + +#: templates/extras/admin/plugins_list.html:23 +msgid "Package Name" +msgstr "" + +#: templates/extras/admin/plugins_list.html:24 +msgid "Author" +msgstr "" + +#: templates/extras/admin/plugins_list.html:25 +msgid "Author Email" +msgstr "" + +#: templates/extras/admin/plugins_list.html:27 +msgid "Version" +msgstr "" + +#: templates/extras/configcontext.html:46 +#: templates/extras/configtemplate.html:38 +#: templates/extras/exporttemplate.html:57 +msgid "The data file associated with this object has been deleted" +msgstr "" + +#: templates/extras/configcontext.html:55 +#: templates/extras/configtemplate.html:47 +#: templates/extras/exporttemplate.html:66 +msgid "Data Synced" +msgstr "" + +#: templates/extras/configcontext_list.html:7 +#: templates/extras/configtemplate_list.html:7 +#: templates/extras/exporttemplate_list.html:7 +msgid "Sync Data" +msgstr "" + +#: templates/extras/configrevision.html:47 +msgid "Default unit height" +msgstr "" + +#: templates/extras/configrevision.html:51 +msgid "Default unit width" +msgstr "" + +#: templates/extras/configrevision.html:63 +msgid "Default voltage" +msgstr "" + +#: templates/extras/configrevision.html:67 +msgid "Default amperage" +msgstr "" + +#: templates/extras/configrevision.html:71 +msgid "Default max utilization" +msgstr "" + +#: templates/extras/configrevision.html:83 +msgid "Enforce global unique" +msgstr "" + +#: templates/extras/configrevision.html:135 +msgid "Paginate count" +msgstr "" + +#: templates/extras/configrevision.html:139 +msgid "Max page size" +msgstr "" + +#: templates/extras/configrevision.html:163 +msgid "Default user preferences" +msgstr "" + +#: templates/extras/configrevision.html:187 +msgid "Job retention" +msgstr "" + +#: templates/extras/configrevision.html:199 +msgid "Comment" +msgstr "" + +#: templates/extras/configrevision_restore.html:8 +#: templates/extras/configrevision_restore.html:43 +#: templates/extras/configrevision_restore.html:79 +msgid "Restore" +msgstr "" + +#: templates/extras/configrevision_restore.html:21 +msgid "Config revisions" +msgstr "" + +#: templates/extras/configrevision_restore.html:54 +msgid "Parameter" +msgstr "" + +#: templates/extras/configrevision_restore.html:55 +msgid "Current Value" +msgstr "" + +#: templates/extras/configrevision_restore.html:56 +msgid "New Value" +msgstr "" + +#: templates/extras/configrevision_restore.html:66 +msgid "Changed" +msgstr "" + +#: templates/extras/configtemplate.html:58 +msgid "Environment Parameters" +msgstr "" + +#: templates/extras/configtemplate.html:69 +#: templates/extras/exporttemplate.html:88 +msgid "Template" +msgstr "" + +#: templates/extras/customfield.html:31 templates/extras/customlink.html:22 +msgid "Group Name" +msgstr "" + +#: templates/extras/customfield.html:43 +msgid "Cloneable" +msgstr "" + +#: templates/extras/customfield.html:53 +msgid "Default Value" +msgstr "" + +#: templates/extras/customfield.html:64 +msgid "Search Weight" +msgstr "" + +#: templates/extras/customfield.html:74 +msgid "Filter Logic" +msgstr "" + +#: templates/extras/customfield.html:78 +msgid "Display Weight" +msgstr "" + +#: templates/extras/customfield.html:104 +msgid "Validation Rules" +msgstr "" + +#: templates/extras/customfield.html:108 +msgid "Minimum Value" +msgstr "" + +#: templates/extras/customfield.html:112 +msgid "Maximum Value" +msgstr "" + +#: templates/extras/customfield.html:116 +msgid "Regular Expression" +msgstr "" + +#: templates/extras/customlink.html:30 +msgid "Button Class" +msgstr "" + +#: templates/extras/customlink.html:41 templates/extras/exporttemplate.html:73 +#: templates/extras/savedfilter.html:41 templates/extras/webhook.html:102 +msgid "Assigned Models" +msgstr "" + +#: templates/extras/customlink.html:57 +msgid "Link Text" +msgstr "" + +#: templates/extras/customlink.html:65 +msgid "Link URL" +msgstr "" + +#: templates/extras/dashboard/reset.html:4 templates/home.html:63 +msgid "Reset Dashboard" +msgstr "" + +#: templates/extras/dashboard/reset.html:8 +msgid "" +"This will remove all configured widgets and restore the " +"default dashboard configuration." +msgstr "" + +#: templates/extras/dashboard/reset.html:13 +msgid "" +"This change affects only your dashboard, and will not impact other " +"users." +msgstr "" + +#: templates/extras/dashboard/widget_add.html:7 +msgid "Add a Widget" +msgstr "" + +#: templates/extras/dashboard/widgets/bookmarks.html:14 +msgid "No bookmarks have been added yet." +msgstr "" + +#: templates/extras/dashboard/widgets/objectcounts.html:15 +msgid "No permission" +msgstr "" + +#: templates/extras/dashboard/widgets/objectlist.html:6 +msgid "No permission to view this content" +msgstr "" + +#: templates/extras/dashboard/widgets/objectlist.html:10 +msgid "Unable to load content. Invalid view name" +msgstr "" + +#: templates/extras/dashboard/widgets/rssfeed.html:12 +msgid "No content found" +msgstr "" + +#: templates/extras/dashboard/widgets/rssfeed.html:18 +msgid "There was a problem fetching the RSS feed" +msgstr "" + +#: templates/extras/dashboard/widgets/rssfeed.html:21 +msgid "HTTP" +msgstr "" + +#: templates/extras/exporttemplate.html:29 +msgid "MIME Type" +msgstr "" + +#: templates/extras/exporttemplate.html:33 +msgid "File Extension" +msgstr "" + +#: templates/extras/htmx/report_result.html:9 +#: templates/extras/htmx/script_result.html:10 +msgid "Scheduled for" +msgstr "" + +#: templates/extras/htmx/report_result.html:14 +#: templates/extras/htmx/script_result.html:15 +msgid "Duration" +msgstr "" + +#: templates/extras/htmx/report_result.html:20 +msgid "Report Methods" +msgstr "" + +#: templates/extras/htmx/report_result.html:38 +msgid "Report Results" +msgstr "" + +#: templates/extras/htmx/report_result.html:44 +#: templates/extras/htmx/script_result.html:26 +msgid "Level" +msgstr "" + +#: templates/extras/htmx/report_result.html:46 +#: templates/extras/htmx/script_result.html:27 +msgid "Message" +msgstr "" + +#: templates/extras/htmx/script_result.html:21 +msgid "Script Log" +msgstr "" + +#: templates/extras/htmx/script_result.html:25 +msgid "Line" +msgstr "" + +#: templates/extras/htmx/script_result.html:38 +msgid "No log output" +msgstr "" + +#: templates/extras/htmx/script_result.html:46 +msgid "Exec Time" +msgstr "" + +#: templates/extras/htmx/script_result.html:46 +msgctxt "Unit of time" +msgid "seconds" +msgstr "" + +#: templates/extras/htmx/script_result.html:50 +msgid "Output" +msgstr "" + +#: templates/extras/inc/result_pending.html:4 +msgid "Loading" +msgstr "" + +#: templates/extras/inc/result_pending.html:6 +msgid "Results pending" +msgstr "" + +#: templates/extras/journalentry.html:16 +msgid "Journal Entry" +msgstr "" + +#: templates/extras/object_changelog.html:15 +#: templates/extras/objectchange_list.html:9 +msgid "Change log retention" +msgstr "" + +#: templates/extras/object_changelog.html:15 +#: templates/extras/objectchange_list.html:9 +msgid "days" +msgstr "" + +#: templates/extras/object_changelog.html:15 +#: templates/extras/objectchange_list.html:9 +msgid "Indefinite" +msgstr "" + +#: templates/extras/object_configcontext.html:11 +msgid "Rendered Context" +msgstr "" + +#: templates/extras/object_configcontext.html:22 +msgid "Local Context" +msgstr "" + +#: templates/extras/object_configcontext.html:34 +msgid "The local config context overwrites all source contexts" +msgstr "" + +#: templates/extras/object_configcontext.html:40 +msgid "Source Contexts" +msgstr "" + +#: templates/extras/object_journal.html:18 +msgid "New Journal Entry" +msgstr "" + +#: templates/extras/objectchange.html:29 +#: templates/users/objectpermission.html:45 +msgid "Change" +msgstr "" + +#: templates/extras/objectchange.html:84 +msgid "Difference" +msgstr "" + +#: templates/extras/objectchange.html:87 +msgid "Previous" +msgstr "" + +#: templates/extras/objectchange.html:90 +msgid "Next" +msgstr "" + +#: templates/extras/objectchange.html:98 +msgid "Object Created" +msgstr "" + +#: templates/extras/objectchange.html:100 +msgid "Object Deleted" +msgstr "" + +#: templates/extras/objectchange.html:102 +msgid "No Changes" +msgstr "" + +#: templates/extras/objectchange.html:117 +msgid "Pre-Change Data" +msgstr "" + +#: templates/extras/objectchange.html:126 +msgid "Warning: Comparing non-atomic change to previous change record" +msgstr "" + +#: templates/extras/objectchange.html:136 +msgid "Post-Change Data" +msgstr "" + +#: templates/extras/objectchange.html:157 +#, python-format +msgid "See All %(count)s Changes" +msgstr "" + +#: templates/extras/report.html:14 +msgid "This report is invalid and cannot be run." +msgstr "" + +#: templates/extras/report.html:23 templates/extras/report_list.html:88 +msgid "Run Again" +msgstr "" + +#: templates/extras/report.html:25 templates/extras/report_list.html:90 +msgid "Run Report" +msgstr "" + +#: templates/extras/report.html:36 +msgid "Last run" +msgstr "" + +#: templates/extras/report/base.html:30 +msgid "Report" +msgstr "" + +#: templates/extras/report_list.html:48 templates/extras/script_list.html:54 +msgid "Last Run" +msgstr "" + +#: templates/extras/report_list.html:70 templates/extras/script_list.html:77 +msgid "Never" +msgstr "" + +#: templates/extras/report_list.html:75 +msgid "Report has no test methods" +msgstr "" + +#: templates/extras/report_list.html:76 +msgid "Invalid" +msgstr "" + +#: templates/extras/report_list.html:125 +msgid "No Reports Found" +msgstr "" + +#: templates/extras/report_list.html:128 +#, python-format +msgid "" +"Get started by creating a report from " +"an uploaded file or data source." +msgstr "" + +#: templates/extras/script.html:13 +msgid "You do not have permission to run scripts" +msgstr "" + +#: templates/extras/script.html:37 +msgid "Run Script" +msgstr "" + +#: templates/extras/script/base.html:29 +msgid "Script" +msgstr "" + +#: templates/extras/script_list.html:44 +#, python-format +msgid "" +"Script file at %(file_path)s could not be loaded." +msgstr "" + +#: templates/extras/script_list.html:91 +msgid "No Scripts Found" +msgstr "" + +#: templates/extras/script_list.html:94 +#, python-format +msgid "" +"Get started by creating a script from " +"an uploaded file or data source." +msgstr "" + +#: templates/extras/script_result.html:42 +msgid "Log" +msgstr "" + +#: templates/extras/tag.html:35 +msgid "Tagged Items" +msgstr "" + +#: templates/extras/tag.html:47 +msgid "Allowed Object Types" +msgstr "" + +#: templates/extras/tag.html:56 +msgid "Any" +msgstr "" + +#: templates/extras/tag.html:63 +msgid "Tagged Item Types" +msgstr "" + +#: templates/extras/tag.html:89 +msgid "Tagged Objects" +msgstr "" + +#: templates/extras/webhook.html:45 +msgid "Job start" +msgstr "" + +#: templates/extras/webhook.html:49 +msgid "Job end" +msgstr "" + +#: templates/extras/webhook.html:62 +msgid "HTTP Method" +msgstr "" + +#: templates/extras/webhook.html:70 +msgid "HTTP Content Type" +msgstr "" + +#: templates/extras/webhook.html:87 +msgid "SSL Verification" +msgstr "" + +#: templates/extras/webhook.html:128 +msgid "Additional Headers" +msgstr "" + +#: templates/extras/webhook.html:140 +msgid "Body Template" +msgstr "" + +#: templates/generic/bulk_add_component.html:15 +msgid "Bulk Creation" +msgstr "" + +#: templates/generic/bulk_add_component.html:20 +#: templates/generic/bulk_edit.html:28 +msgid "Selected Objects" +msgstr "" + +#: templates/generic/bulk_add_component.html:46 +msgid "to Add" +msgstr "" + +#: templates/generic/bulk_delete.html:24 +msgid "Confirm Bulk Deletion" +msgstr "" + +#: templates/generic/bulk_delete.html:26 +msgctxt "Noun" +msgid "Warning" +msgstr "" + +#: templates/generic/bulk_delete.html:27 +#, python-format +msgid "" +"The following operation will delete %(count)s " +"%(type_plural)s. Please carefully review the objects to be deleted and " +"confirm below." +msgstr "" + +#: templates/generic/bulk_edit.html:16 templates/generic/object_edit.html:17 +msgid "Editing" +msgstr "" + +#: templates/generic/bulk_edit.html:23 +msgid "Bulk Edit" +msgstr "" + +#: templates/generic/bulk_edit.html:124 templates/generic/bulk_rename.html:42 +msgid "Apply" +msgstr "" + +#: templates/generic/bulk_import.html:14 +msgid "Bulk Import" +msgstr "" + +#: templates/generic/bulk_import.html:20 +msgid "Direct Import" +msgstr "" + +#: templates/generic/bulk_import.html:25 +msgid "Upload File" +msgstr "" + +#: templates/generic/bulk_import.html:51 templates/generic/bulk_import.html:73 +#: templates/generic/bulk_import.html:95 +msgid "Submit" +msgstr "" + +#: templates/generic/bulk_import.html:110 +msgid "Field Options" +msgstr "" + +#: templates/generic/bulk_import.html:117 +msgid "Accessor" +msgstr "" + +#: templates/generic/bulk_import.html:154 +msgid "Import Value" +msgstr "" + +#: templates/generic/bulk_import.html:181 +msgid "Format: YYYY-MM-DD" +msgstr "" + +#: templates/generic/bulk_import.html:183 +msgid "Specify true or false" +msgstr "" + +#: templates/generic/bulk_import.html:195 +msgid "Required fields must be specified for all objects." +msgstr "" + +#: templates/generic/bulk_import.html:201 +#, python-format +msgid "" +"Related objects may be referenced by any unique attribute. For example, " +"%(example)s would identify a VRF by its route distinguisher." +msgstr "" + +#: templates/generic/bulk_remove.html:13 +msgid "Confirm Bulk Removal" +msgstr "" + +#: templates/generic/bulk_remove.html:15 +#, python-format +msgid "" +"Warning: The following operation will remove %(count)s " +"%(obj_type_plural)s from %(parent_obj)s." +msgstr "" + +#: templates/generic/bulk_remove.html:21 +#, python-format +msgid "" +"Please carefully review the %(obj_type_plural)s to be removed and confirm " +"below." +msgstr "" + +#: templates/generic/bulk_remove.html:38 +#, python-format +msgid "Delete these %(count)s %(obj_type_plural)s" +msgstr "" + +#: templates/generic/bulk_rename.html:7 +msgid "Renaming" +msgstr "" + +#: templates/generic/bulk_rename.html:16 +msgid "Current Name" +msgstr "" + +#: templates/generic/bulk_rename.html:17 +msgid "New Name" +msgstr "" + +#: templates/generic/bulk_rename.html:40 +#: utilities/templates/widgets/markdown_input.html:11 +msgid "Preview" +msgstr "" + +#: templates/generic/confirmation_form.html:16 +msgid "Are you sure" +msgstr "" + +#: templates/generic/confirmation_form.html:19 +msgid "Confirm" +msgstr "" + +#: templates/generic/object.html:51 +msgid "ago" +msgstr "" + +#: templates/generic/object_children.html:27 +#: utilities/templates/buttons/bulk_edit.html:4 +msgid "Edit Selected" +msgstr "" + +#: templates/generic/object_children.html:41 +#: utilities/templates/buttons/bulk_delete.html:4 +msgid "Delete Selected" +msgstr "" + +#: templates/generic/object_edit.html:19 +#, python-format +msgid "Add a new %(object_type)s" +msgstr "" + +#: templates/generic/object_edit.html:47 +msgid "View model documentation" +msgstr "" + +#: templates/generic/object_edit.html:48 +msgid "Help" +msgstr "" + +#: templates/generic/object_edit.html:73 +msgid "Create & Add Another" +msgstr "" + +#: templates/generic/object_list.html:48 templates/search.html:13 +msgid "Results" +msgstr "" + +#: templates/generic/object_list.html:54 +msgid "Filters" +msgstr "" + +#: templates/generic/object_list.html:94 +#, python-format +msgid "" +"Select all %(count)s %(object_type_plural)s matching query" +msgstr "" + +#: templates/home.html:12 +msgid "New Release Available" +msgstr "" + +#: templates/home.html:14 +msgid "is available" +msgstr "" + +#: templates/home.html:17 +msgctxt "Document title" +msgid "Upgrade Instructions" +msgstr "" + +#: templates/home.html:37 +msgid "Unlock Dashboard" +msgstr "" + +#: templates/home.html:46 +msgid "Lock Dashboard" +msgstr "" + +#: templates/home.html:57 +msgid "Add Widget" +msgstr "" + +#: templates/home.html:60 +msgid "Save Layout" +msgstr "" + +#: templates/htmx/delete_form.html:7 +msgid "Confirm Deletion" +msgstr "" + +#: templates/htmx/delete_form.html:11 +#, python-format +msgid "" +"Are you sure you want to delete " +"%(object_type)s %(object)s?" +msgstr "" + +#: templates/htmx/object_selector.html:5 +msgid "Select" +msgstr "" + +#: templates/inc/filter_list.html:50 +#: utilities/templates/helpers/table_config_form.html:39 +msgid "Reset" +msgstr "" + +#: templates/inc/missing_prerequisites.html:7 +#, python-format +msgid "" +"Before you can add a %(model)s you must first create a " +"%(prerequisite_model)s." +msgstr "" + +#: templates/inc/paginator.html:38 templates/inc/paginator_htmx.html:53 +msgid "Per Page" +msgstr "" + +#: templates/inc/paginator.html:49 templates/inc/paginator_htmx.html:69 +#, python-format +msgid "Showing %(start)s-%(end)s of %(total)s" +msgstr "" + +#: templates/inc/panels/image_attachments.html:10 +msgid "Attach an image" +msgstr "" + +#: templates/inc/panels/related_objects.html:5 +msgid "Related Objects" +msgstr "" + +#: templates/inc/panels/tags.html:11 +msgid "No tags assigned" +msgstr "" + +#: templates/inc/profile_button.html:12 templates/inc/profile_button.html:62 +msgid "Dark Mode" +msgstr "" + +#: templates/inc/profile_button.html:45 +msgid "Log Out" +msgstr "" + +#: templates/inc/profile_button.html:53 +msgid "Log In" +msgstr "" + +#: templates/inc/sync_warning.html:7 +msgid "Data is out of sync with upstream file" +msgstr "" + +#: templates/inc/table_controls_htmx.html:16 +#: templates/inc/table_controls_htmx.html:18 +msgid "Configure Table" +msgstr "" + +#: templates/ipam/aggregate.html:15 templates/ipam/ipaddress.html:17 +#: templates/ipam/iprange.html:16 templates/ipam/prefix.html:15 +msgid "Family" +msgstr "" + +#: templates/ipam/aggregate.html:40 +msgid "Date Added" +msgstr "" + +#: templates/ipam/aggregate/prefixes.html:8 +#: templates/ipam/prefix/prefixes.html:8 templates/ipam/role.html:10 +msgid "Add Prefix" +msgstr "" + +#: templates/ipam/asn.html:24 +msgid "AS Number" +msgstr "" + +#: templates/ipam/fhrpgroup.html:55 +msgid "Authentication Type" +msgstr "" + +#: templates/ipam/fhrpgroup.html:59 +msgid "Authentication Key" +msgstr "" + +#: templates/ipam/fhrpgroup.html:72 +msgid "Virtual IP Addresses" +msgstr "" + +#: templates/ipam/fhrpgroupassignment_edit.html:8 +msgid "FHRP Group Assignment" +msgstr "" + +#: templates/ipam/inc/ipaddress_edit_header.html:19 +msgid "Assign IP" +msgstr "" + +#: templates/ipam/inc/ipaddress_edit_header.html:28 +msgid "Bulk Create" +msgstr "" + +#: templates/ipam/inc/panels/fhrp_groups.html:12 +msgid "Virtual IPs" +msgstr "" + +#: templates/ipam/inc/panels/fhrp_groups.html:52 +msgid "Create Group" +msgstr "" + +#: templates/ipam/inc/panels/fhrp_groups.html:57 +msgid "Assign Group" +msgstr "" + +#: templates/ipam/inc/toggle_available.html:7 +msgid "Show Assigned" +msgstr "" + +#: templates/ipam/inc/toggle_available.html:10 +msgid "Show Available" +msgstr "" + +#: templates/ipam/inc/toggle_available.html:13 +msgid "Show All" +msgstr "" + +#: templates/ipam/ipaddress.html:26 templates/ipam/iprange.html:48 +#: templates/ipam/prefix.html:24 +msgid "Global" +msgstr "" + +#: templates/ipam/ipaddress.html:88 +msgid "NAT (outside)" +msgstr "" + +#: templates/ipam/ipaddress_assign.html:8 +msgid "Assign an IP Address" +msgstr "" + +#: templates/ipam/ipaddress_assign.html:23 +msgid "Select IP Address" +msgstr "" + +#: templates/ipam/ipaddress_assign.html:39 +msgid "Search Results" +msgstr "" + +#: templates/ipam/ipaddress_bulk_add.html:6 +msgid "Bulk Add IP Addresses" +msgstr "" + +#: templates/ipam/ipaddress_edit.html:35 +msgid "Interface Assignment" +msgstr "" + +#: templates/ipam/ipaddress_edit.html:74 +msgid "NAT IP (Inside" +msgstr "" + +#: templates/ipam/iprange.html:20 +msgid "Starting Address" +msgstr "" + +#: templates/ipam/iprange.html:24 +msgid "Ending Address" +msgstr "" + +#: templates/ipam/iprange.html:36 templates/ipam/prefix.html:104 +msgid "Marked fully utilized" +msgstr "" + +#: templates/ipam/l2vpn.html:11 templates/ipam/l2vpntermination.html:10 +msgid "L2VPN Attributes" +msgstr "" + +#: templates/ipam/l2vpn.html:65 +msgid "Add a Termination" +msgstr "" + +#: templates/ipam/l2vpntermination_edit.html:9 +msgid "L2VPN Termination" +msgstr "" + +#: templates/ipam/prefix.html:112 +msgid "Child IPs" +msgstr "" + +#: templates/ipam/prefix.html:120 +msgid "Available IPs" +msgstr "" + +#: templates/ipam/prefix.html:132 +msgid "First available IP" +msgstr "" + +#: templates/ipam/prefix.html:151 +msgid "Addressing Details" +msgstr "" + +#: templates/ipam/prefix.html:181 +msgid "Prefix Details" +msgstr "" + +#: templates/ipam/prefix.html:187 +msgid "Network Address" +msgstr "" + +#: templates/ipam/prefix.html:191 +msgid "Network Mask" +msgstr "" + +#: templates/ipam/prefix.html:195 +msgid "Wildcard Mask" +msgstr "" + +#: templates/ipam/prefix.html:199 +msgid "Broadcast Address" +msgstr "" + +#: templates/ipam/prefix/ip_ranges.html:7 +msgid "Add IP Range" +msgstr "" + +#: templates/ipam/prefix_list.html:7 +msgid "Hide Depth Indicators" +msgstr "" + +#: templates/ipam/prefix_list.html:11 +msgid "Max Depth" +msgstr "" + +#: templates/ipam/prefix_list.html:28 +msgid "Max Length" +msgstr "" + +#: templates/ipam/rir.html:10 +msgid "Add Aggregate" +msgstr "" + +#: templates/ipam/routetarget.html:10 +msgid "Route Target" +msgstr "" + +#: templates/ipam/routetarget.html:40 +msgid "Importing VRFs" +msgstr "" + +#: templates/ipam/routetarget.html:49 +msgid "Exporting VRFs" +msgstr "" + +#: templates/ipam/routetarget.html:60 +msgid "Importing L2VPNs" +msgstr "" + +#: templates/ipam/routetarget.html:69 +msgid "Exporting L2VPNs" +msgstr "" + +#: templates/ipam/service.html:22 templates/ipam/service_create.html:8 +#: templates/ipam/service_edit.html:8 +msgid "Service" +msgstr "" + +#: templates/ipam/service_create.html:43 +msgid "From Template" +msgstr "" + +#: templates/ipam/service_create.html:48 +msgid "Custom" +msgstr "" + +#: templates/ipam/service_edit.html:37 +msgid "Port(s)" +msgstr "" + +#: templates/ipam/vlan.html:95 +msgid "Add a Prefix" +msgstr "" + +#: templates/ipam/vlangroup.html:18 +msgid "Add VLAN" +msgstr "" + +#: templates/ipam/vlangroup.html:43 +msgid "Permitted VIDs" +msgstr "" + +#: templates/ipam/vrf.html:19 +msgid "Route Distinguisher" +msgstr "" + +#: templates/ipam/vrf.html:32 +msgid "Unique IP Space" +msgstr "" + +#: templates/login.html:20 +#: utilities/templates/form_helpers/render_errors.html:7 +msgid "Errors" +msgstr "" + +#: templates/login.html:48 +msgid "Sign In" +msgstr "" + +#: templates/login.html:54 +msgid "Or use a single sign-on (SSO) provider" +msgstr "" + +#: templates/login.html:68 +msgid "Toggle Color Mode" +msgstr "" + +#: templates/media_failure.html:7 +msgid "Static Media Failure - NetBox" +msgstr "" + +#: templates/media_failure.html:21 +msgid "Static Media Failure" +msgstr "" + +#: templates/media_failure.html:23 +msgid "The following static media file failed to load" +msgstr "" + +#: templates/media_failure.html:26 +msgid "Check the following" +msgstr "" + +#: templates/media_failure.html:29 +msgid "" +"manage.py collectstatic was run during the most recent upgrade. " +"This installs the most recent iteration of each static file into the static " +"root path." +msgstr "" + +#: templates/media_failure.html:35 +#, python-format +msgid "" +"The HTTP service (e.g. nginx or Apache) is configured to serve files from " +"the STATIC_ROOT path. Refer to the " +"installation documentation for further guidance." +msgstr "" + +#: templates/media_failure.html:47 +#, python-format +msgid "" +"The file %(filename)s exists in the static root directory and " +"is readable by the HTTP server." +msgstr "" + +#: templates/media_failure.html:55 +#, python-format +msgid "" +"Click here to attempt loading NetBox again." +msgstr "" + +#: templates/tenancy/contact.html:18 tenancy/filtersets.py:123 +#: tenancy/forms/bulk_edit.py:136 tenancy/forms/filtersets.py:103 +#: tenancy/forms/forms.py:56 tenancy/forms/model_forms.py:112 +#: tenancy/forms/model_forms.py:135 tenancy/tables/contacts.py:98 +msgid "Contact" +msgstr "" + +#: templates/tenancy/contact.html:30 tenancy/forms/bulk_edit.py:98 +msgid "Title" +msgstr "" + +#: templates/tenancy/contact.html:34 tenancy/forms/bulk_edit.py:103 +#: tenancy/tables/contacts.py:64 +msgid "Phone" +msgstr "" + +#: templates/tenancy/contact.html:86 tenancy/tables/contacts.py:73 +msgid "Assignments" +msgstr "" + +#: templates/tenancy/contactassignment_edit.html:12 +msgid "Contact Assignment" +msgstr "" + +#: templates/tenancy/contactgroup.html:19 tenancy/forms/forms.py:66 +#: tenancy/forms/model_forms.py:79 +msgid "Contact Group" +msgstr "" + +#: templates/tenancy/contactgroup.html:57 +msgid "Add Contact Group" +msgstr "" + +#: templates/tenancy/contactrole.html:15 tenancy/filtersets.py:128 +#: tenancy/forms/forms.py:61 tenancy/forms/model_forms.py:93 +msgid "Contact Role" +msgstr "" + +#: templates/tenancy/object_contacts.html:9 +msgid "Add a contact" +msgstr "" + +#: templates/tenancy/tenantgroup.html:17 +msgid "Add Tenant" +msgstr "" + +#: templates/tenancy/tenantgroup.html:27 tenancy/forms/model_forms.py:34 +#: tenancy/tables/columns.py:51 tenancy/tables/columns.py:61 +msgid "Tenant Group" +msgstr "" + +#: templates/tenancy/tenantgroup.html:66 +msgid "Add Tenant Group" +msgstr "" + +#: templates/users/group.html:37 templates/users/user.html:61 +msgid "Assigned Permissions" +msgstr "" + +#: templates/users/objectpermission.html:6 +#: templates/users/objectpermission.html:14 users/forms/filtersets.py:69 +msgid "Permission" +msgstr "" + +#: templates/users/objectpermission.html:33 users/forms/filtersets.py:70 +#: users/forms/model_forms.py:321 +msgid "Actions" +msgstr "" + +#: templates/users/objectpermission.html:37 +msgid "View" +msgstr "" + +#: templates/users/objectpermission.html:56 users/forms/model_forms.py:324 +msgid "Constraints" +msgstr "" + +#: templates/users/objectpermission.html:76 +msgid "Assigned Users" +msgstr "" + +#: templates/users/user.html:38 +msgid "Staff" +msgstr "" + +#: templates/virtualization/cluster.html:56 +msgid "Allocated Resources" +msgstr "" + +#: templates/virtualization/cluster.html:60 +#: templates/virtualization/virtualmachine.html:128 +msgid "Virtual CPUs" +msgstr "" + +#: templates/virtualization/cluster.html:64 +#: templates/virtualization/virtualmachine.html:132 +msgid "Memory" +msgstr "" + +#: templates/virtualization/cluster.html:74 +#: templates/virtualization/virtualmachine.html:142 +msgid "Disk Space" +msgstr "" + +#: templates/virtualization/cluster.html:77 +#: templates/virtualization/virtualmachine.html:145 +msgctxt "Abbreviation for gigabyte" +msgid "GB" +msgstr "" + +#: templates/virtualization/cluster/base.html:18 +msgid "Add Virtual Machine" +msgstr "" + +#: templates/virtualization/cluster/base.html:24 +msgid "Assign Device" +msgstr "" + +#: templates/virtualization/cluster/devices.html:10 +msgid "Remove Selected" +msgstr "" + +#: templates/virtualization/cluster_add_devices.html:9 +#, python-format +msgid "Add Device to Cluster %(cluster)s" +msgstr "" + +#: templates/virtualization/cluster_add_devices.html:23 +msgid "Device Selection" +msgstr "" + +#: templates/virtualization/cluster_add_devices.html:31 +msgid "Add Devices" +msgstr "" + +#: templates/virtualization/clustergroup.html:10 +#: templates/virtualization/clustertype.html:10 +msgid "Add Cluster" +msgstr "" + +#: templates/virtualization/clustergroup.html:20 +#: virtualization/forms/model_forms.py:50 +msgid "Cluster Group" +msgstr "" + +#: templates/virtualization/clustertype.html:20 +#: templates/virtualization/virtualmachine.html:111 +#: virtualization/forms/model_forms.py:34 +msgid "Cluster Type" +msgstr "" + +#: templates/virtualization/virtualmachine.html:124 +#: virtualization/forms/bulk_edit.py:187 +#: virtualization/forms/model_forms.py:225 +msgid "Resources" +msgstr "" + +#: templates/wireless/inc/authentication_attrs.html:13 +msgid "Cipher" +msgstr "" + +#: templates/wireless/inc/authentication_attrs.html:17 +msgid "PSK" +msgstr "" + +#: templates/wireless/inc/authentication_attrs.html:21 +msgid "Show Secret" +msgstr "" + +#: templates/wireless/inc/wirelesslink_interface.html:35 +#: templates/wireless/inc/wirelesslink_interface.html:45 +msgctxt "Abbreviation for megahertz" +msgid "MHz" +msgstr "" + +#: templates/wireless/wirelesslan.html:11 wireless/forms/model_forms.py:54 +msgid "Wireless LAN" +msgstr "" + +#: templates/wireless/wirelesslan.html:59 +msgid "Attached Interfaces" +msgstr "" + +#: templates/wireless/wirelesslangroup.html:17 +msgid "Add Wireless LAN" +msgstr "" + +#: templates/wireless/wirelesslangroup.html:26 wireless/forms/model_forms.py:27 +msgid "Wireless LAN Group" +msgstr "" + +#: templates/wireless/wirelesslangroup.html:64 +msgid "Add Wireless LAN Group" +msgstr "" + +#: templates/wireless/wirelesslink.html:16 +msgid "Link Properties" +msgstr "" + +#: tenancy/choices.py:19 +msgid "Tertiary" +msgstr "" + +#: tenancy/choices.py:20 +msgid "Inactive" +msgstr "" + +#: tenancy/filtersets.py:30 tenancy/filtersets.py:56 +msgid "Contact group (ID)" +msgstr "" + +#: tenancy/filtersets.py:36 tenancy/filtersets.py:63 +msgid "Contact group (slug)" +msgstr "" + +#: tenancy/filtersets.py:92 +msgid "Contact (ID)" +msgstr "" + +#: tenancy/filtersets.py:96 +msgid "Contact role (ID)" +msgstr "" + +#: tenancy/filtersets.py:102 +msgid "Contact role (slug)" +msgstr "" + +#: tenancy/filtersets.py:134 +msgid "Contact group" +msgstr "" + +#: tenancy/filtersets.py:145 tenancy/filtersets.py:164 +msgid "Tenant group (ID)" +msgstr "" + +#: tenancy/filtersets.py:197 +msgid "Tenant Group (ID)" +msgstr "" + +#: tenancy/filtersets.py:204 +msgid "Tenant Group (slug)" +msgstr "" + +#: tenancy/forms/bulk_edit.py:65 +msgid "Desciption" +msgstr "" + +#: tenancy/forms/bulk_import.py:101 +msgid "Assigned contact" +msgstr "" + +#: tenancy/models/contacts.py:31 +msgid "contact group" +msgstr "" + +#: tenancy/models/contacts.py:32 +msgid "contact groups" +msgstr "" + +#: tenancy/models/contacts.py:47 +msgid "contact role" +msgstr "" + +#: tenancy/models/contacts.py:48 +msgid "contact roles" +msgstr "" + +#: tenancy/models/contacts.py:67 +msgid "title" +msgstr "" + +#: tenancy/models/contacts.py:72 +msgid "phone" +msgstr "" + +#: tenancy/models/contacts.py:77 +msgid "email" +msgstr "" + +#: tenancy/models/contacts.py:86 +msgid "link" +msgstr "" + +#: tenancy/models/contacts.py:102 +msgid "contact" +msgstr "" + +#: tenancy/models/contacts.py:103 +msgid "contacts" +msgstr "" + +#: tenancy/models/contacts.py:149 +msgid "contact assignment" +msgstr "" + +#: tenancy/models/contacts.py:150 +msgid "contact assignments" +msgstr "" + +#: tenancy/models/tenants.py:32 +msgid "tenant group" +msgstr "" + +#: tenancy/models/tenants.py:33 +msgid "tenant groups" +msgstr "" + +#: tenancy/models/tenants.py:70 +msgid "Tenant name must be unique per group." +msgstr "" + +#: tenancy/models/tenants.py:80 +msgid "Tenant slug must be unique per group." +msgstr "" + +#: tenancy/models/tenants.py:88 +msgid "tenant" +msgstr "" + +#: tenancy/models/tenants.py:89 +msgid "tenants" +msgstr "" + +#: tenancy/tables/contacts.py:107 +msgid "Contact Title" +msgstr "" + +#: tenancy/tables/contacts.py:111 +msgid "Contact Phone" +msgstr "" + +#: tenancy/tables/contacts.py:115 +msgid "Contact Email" +msgstr "" + +#: tenancy/tables/contacts.py:119 +msgid "Contact Address" +msgstr "" + +#: tenancy/tables/contacts.py:123 +msgid "Contact Link" +msgstr "" + +#: tenancy/tables/contacts.py:127 +msgid "Contact Description" +msgstr "" + +#: users/filtersets.py:48 users/filtersets.py:151 +msgid "Group (name)" +msgstr "" + +#: users/forms/bulk_edit.py:24 +msgid "First name" +msgstr "" + +#: users/forms/bulk_edit.py:29 +msgid "Last name" +msgstr "" + +#: users/forms/bulk_edit.py:41 +msgid "Staff status" +msgstr "" + +#: users/forms/bulk_edit.py:46 +msgid "Superuser status" +msgstr "" + +#: users/forms/bulk_import.py:43 +msgid "If no key is provided, one will be generated automatically." +msgstr "" + +#: users/forms/filtersets.py:54 users/tables.py:42 +msgid "Is Staff" +msgstr "" + +#: users/forms/filtersets.py:61 users/tables.py:45 +msgid "Is Superuser" +msgstr "" + +#: users/forms/filtersets.py:94 users/tables.py:89 +msgid "Can View" +msgstr "" + +#: users/forms/filtersets.py:101 users/tables.py:92 +msgid "Can Add" +msgstr "" + +#: users/forms/filtersets.py:108 users/tables.py:95 +msgid "Can Change" +msgstr "" + +#: users/forms/filtersets.py:115 users/tables.py:98 +msgid "Can Delete" +msgstr "" + +#: users/forms/model_forms.py:58 +msgid "User Interface" +msgstr "" + +#: users/forms/model_forms.py:115 +msgid "" +"Keys must be at least 40 characters in length. Be sure to record " +"your key prior to submitting this form, as it may no longer be " +"accessible once the token has been created." +msgstr "" + +#: users/forms/model_forms.py:127 +msgid "" +"Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for " +"no restrictions. Example: 10.1.1.0/24,192.168.10.16/32,2001:" +"db8:1::/64" +msgstr "" + +#: users/forms/model_forms.py:176 +msgid "Confirm password" +msgstr "" + +#: users/forms/model_forms.py:179 +msgid "Enter the same password as before, for verification." +msgstr "" + +#: users/forms/model_forms.py:237 +msgid "Passwords do not match! Please check your input and try again." +msgstr "" + +#: users/forms/model_forms.py:303 +msgid "Additional actions" +msgstr "" + +#: users/forms/model_forms.py:306 +msgid "Actions granted in addition to those listed above" +msgstr "" + +#: users/forms/model_forms.py:322 +msgid "Objects" +msgstr "" + +#: users/forms/model_forms.py:334 +msgid "" +"JSON expression of a queryset filter that will return only permitted " +"objects. Leave null to match all objects of this type. A list of multiple " +"objects will result in a logical OR operation." +msgstr "" + +#: users/forms/model_forms.py:372 +msgid "At least one action must be selected." +msgstr "" + +#: users/forms/model_forms.py:389 +#, python-brace-format +msgid "Invalid filter for {model}: {error}" +msgstr "" + +#: users/models.py:54 +msgid "user" +msgstr "" + +#: users/models.py:55 +msgid "users" +msgstr "" + +#: users/models.py:66 +msgid "A user with this username already exists." +msgstr "" + +#: users/models.py:78 +msgid "group" +msgstr "" + +#: users/models.py:79 +msgid "groups" +msgstr "" + +#: users/models.py:104 users/models.py:105 +msgid "user preferences" +msgstr "" + +#: users/models.py:172 +#, python-brace-format +msgid "Key '{path}' is a leaf node; cannot assign new keys" +msgstr "" + +#: users/models.py:184 +#, python-brace-format +msgid "Key '{path}' is a dictionary; cannot assign a non-dictionary value" +msgstr "" + +#: users/models.py:249 +msgid "expires" +msgstr "" + +#: users/models.py:254 +msgid "last used" +msgstr "" + +#: users/models.py:259 +msgid "key" +msgstr "" + +#: users/models.py:265 +msgid "write enabled" +msgstr "" + +#: users/models.py:267 +msgid "Permit create/update/delete operations using this key" +msgstr "" + +#: users/models.py:278 +msgid "allowed IPs" +msgstr "" + +#: users/models.py:280 +msgid "" +"Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for " +"no restrictions. Ex: \"10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64\"" +msgstr "" + +#: users/models.py:288 +msgid "token" +msgstr "" + +#: users/models.py:289 +msgid "tokens" +msgstr "" + +#: users/models.py:370 +msgid "The list of actions granted by this permission" +msgstr "" + +#: users/models.py:375 +msgid "constraints" +msgstr "" + +#: users/models.py:376 +msgid "Queryset filter matching the applicable objects of the selected type(s)" +msgstr "" + +#: users/models.py:383 +msgid "permission" +msgstr "" + +#: users/models.py:384 +msgid "permissions" +msgstr "" + +#: users/tables.py:101 +msgid "Custom Actions" +msgstr "" + +#: utilities/choices.py:16 +#, python-brace-format +msgid "{name} has a key defined but CHOICES is not a list" +msgstr "" + +#: utilities/choices.py:135 +msgid "Dark Red" +msgstr "" + +#: utilities/choices.py:138 +msgid "Rose" +msgstr "" + +#: utilities/choices.py:139 +msgid "Fuchsia" +msgstr "" + +#: utilities/choices.py:141 +msgid "Dark Purple" +msgstr "" + +#: utilities/choices.py:144 +msgid "Light Blue" +msgstr "" + +#: utilities/choices.py:147 +msgid "Aqua" +msgstr "" + +#: utilities/choices.py:148 +msgid "Dark Green" +msgstr "" + +#: utilities/choices.py:150 +msgid "Light Green" +msgstr "" + +#: utilities/choices.py:151 +msgid "Lime" +msgstr "" + +#: utilities/choices.py:153 +msgid "Amber" +msgstr "" + +#: utilities/choices.py:155 +msgid "Dark Orange" +msgstr "" + +#: utilities/choices.py:156 +msgid "Brown" +msgstr "" + +#: utilities/choices.py:157 +msgid "Light Grey" +msgstr "" + +#: utilities/choices.py:158 +msgid "Grey" +msgstr "" + +#: utilities/choices.py:159 +msgid "Dark Grey" +msgstr "" + +#: utilities/choices.py:217 +msgid "Direct" +msgstr "" + +#: utilities/choices.py:218 +msgid "Upload" +msgstr "" + +#: utilities/choices.py:230 utilities/choices.py:244 +msgid "Auto-detect" +msgstr "" + +#: utilities/choices.py:245 +msgid "Comma" +msgstr "" + +#: utilities/choices.py:246 +msgid "Semicolon" +msgstr "" + +#: utilities/choices.py:247 +msgid "Tab" +msgstr "" + +#: utilities/fields.py:162 +#, python-format +msgid "" +"%s(%r) is invalid. to_model parameter to CounterCacheField must be a string " +"in the format 'app.model'" +msgstr "" + +#: utilities/fields.py:172 +#, python-format +msgid "" +"%s(%r) is invalid. to_field parameter to CounterCacheField must be a string " +"in the format 'field'" +msgstr "" + +#: utilities/forms/bulk_import.py:24 +msgid "Enter object data in CSV, JSON or YAML format." +msgstr "" + +#: utilities/forms/bulk_import.py:37 +msgid "CSV delimiter" +msgstr "" + +#: utilities/forms/bulk_import.py:38 +msgid "The character which delimits CSV fields. Applies only to CSV format." +msgstr "" + +#: utilities/forms/bulk_import.py:101 +msgid "Unable to detect data format. Please specify." +msgstr "" + +#: utilities/forms/bulk_import.py:124 +msgid "Invalid CSV delimiter" +msgstr "" + +#: utilities/forms/bulk_import.py:168 +msgid "" +"Invalid YAML data. Data must be in the form of multiple documents, or a " +"single document comprising a list of dictionaries." +msgstr "" + +#: utilities/forms/fields/array.py:17 +#, python-brace-format +msgid "" +"Invalid list ({value}). Must be numeric and ranges must be in ascending " +"order." +msgstr "" + +#: utilities/forms/fields/csv.py:44 +#, python-brace-format +msgid "Invalid value for a multiple choice field: {value}" +msgstr "" + +#: utilities/forms/fields/csv.py:57 utilities/forms/fields/csv.py:74 +#, python-format +msgid "Object not found: %(value)s" +msgstr "" + +#: utilities/forms/fields/csv.py:65 +#, python-brace-format +msgid "" +"\"{value}\" is not a unique value for this field; multiple objects were found" +msgstr "" + +#: utilities/forms/fields/csv.py:97 +msgid "Object type must be specified as \".\"" +msgstr "" + +#: utilities/forms/fields/csv.py:101 +msgid "Invalid object type" +msgstr "" + +#: utilities/forms/fields/expandable.py:25 +msgid "" +"Alphanumeric ranges are supported for bulk creation. Mixed cases and types " +"within a single range are not supported (example: [ge,xe]-0/0/[0-9])." +msgstr "" + +#: utilities/forms/fields/expandable.py:46 +msgid "" +"Specify a numeric range to create multiple IPs.
Example: 192.0.2." +"[1,5,100-254]/24" +msgstr "" + +#: utilities/forms/fields/fields.py:31 +#, python-brace-format +msgid "" +" Markdown syntax is supported" +msgstr "" + +#: utilities/forms/fields/fields.py:48 +msgid "URL-friendly unique shorthand" +msgstr "" + +#: utilities/forms/fields/fields.py:99 +msgid "Enter context data in JSON format." +msgstr "" + +#: utilities/forms/fields/fields.py:117 +msgid "MAC address must be in EUI-48 format" +msgstr "" + +#: utilities/forms/forms.py:53 +msgid "Use regular expressions" +msgstr "" + +#: utilities/forms/forms.py:87 +#, python-brace-format +msgid "Unrecognized header: {name}" +msgstr "" + +#: utilities/forms/forms.py:113 +msgid "Available Columns" +msgstr "" + +#: utilities/forms/forms.py:121 +msgid "Selected Columns" +msgstr "" + +#: utilities/forms/mixins.py:101 +msgid "" +"This object has been modified since the form was rendered. Please consult " +"the object's change log for details." +msgstr "" + +#: utilities/templates/builtins/customfield_value.html:30 +msgid "Not defined" +msgstr "" + +#: utilities/templates/buttons/bookmark.html:9 +msgid "Unbookmark" +msgstr "" + +#: utilities/templates/buttons/bookmark.html:13 +msgid "Bookmark" +msgstr "" + +#: utilities/templates/buttons/clone.html:4 +msgid "Clone" +msgstr "" + +#: utilities/templates/buttons/export.html:4 +msgid "Export" +msgstr "" + +#: utilities/templates/buttons/export.html:7 +msgid "Current View" +msgstr "" + +#: utilities/templates/buttons/export.html:8 +msgid "All Data" +msgstr "" + +#: utilities/templates/buttons/export.html:28 +msgid "Add export template" +msgstr "" + +#: utilities/templates/buttons/import.html:4 +msgid "Import" +msgstr "" + +#: utilities/templates/form_helpers/render_field.html:36 +msgid "Copy to clipboard" +msgstr "" + +#: utilities/templates/form_helpers/render_field.html:52 +msgid "This field is required" +msgstr "" + +#: utilities/templates/form_helpers/render_field.html:65 +msgid "Set Null" +msgstr "" + +#: utilities/templates/helpers/applied_filters.html:11 +msgid "Clear all" +msgstr "" + +#: utilities/templates/helpers/table_config_form.html:8 +msgid "Table Configuration" +msgstr "" + +#: utilities/templates/helpers/table_config_form.html:31 +msgid "Move Up" +msgstr "" + +#: utilities/templates/helpers/table_config_form.html:34 +msgid "Move Down" +msgstr "" + +#: utilities/templates/widgets/apiselect.html:7 +msgid "Open selector" +msgstr "" + +#: utilities/templates/widgets/clearable_file_input.html:12 +msgid "None assigned" +msgstr "" + +#: utilities/templates/widgets/markdown_input.html:6 +msgid "Write" +msgstr "" + +#: utilities/templates/widgets/markdown_input.html:20 +msgid "Testing" +msgstr "" + +#: virtualization/filtersets.py:77 +msgid "Parent group (ID)" +msgstr "" + +#: virtualization/filtersets.py:83 +msgid "Parent group (slug)" +msgstr "" + +#: virtualization/filtersets.py:87 virtualization/filtersets.py:137 +msgid "Cluster type (ID)" +msgstr "" + +#: virtualization/filtersets.py:126 +msgid "Cluster group (ID)" +msgstr "" + +#: virtualization/filtersets.py:147 virtualization/filtersets.py:262 +msgid "Cluster (ID)" +msgstr "" + +#: virtualization/forms/bulk_edit.py:163 +#: virtualization/models/virtualmachines.py:112 +msgid "vCPUs" +msgstr "" + +#: virtualization/forms/bulk_edit.py:167 +msgid "Memory (MB)" +msgstr "" + +#: virtualization/forms/bulk_edit.py:171 +msgid "Disk (GB)" +msgstr "" + +#: virtualization/forms/bulk_import.py:43 +msgid "Type of cluster" +msgstr "" + +#: virtualization/forms/bulk_import.py:50 +msgid "Assigned cluster group" +msgstr "" + +#: virtualization/forms/bulk_import.py:95 +msgid "Assigned cluster" +msgstr "" + +#: virtualization/forms/bulk_import.py:102 +msgid "Assigned device within cluster" +msgstr "" + +#: virtualization/forms/model_forms.py:155 +#, python-brace-format +msgid "" +"{device} belongs to a different site ({device_site}) than the cluster " +"({cluster_site})" +msgstr "" + +#: virtualization/forms/model_forms.py:194 +msgid "Optionally pin this VM to a specific host device within the cluster" +msgstr "" + +#: virtualization/forms/model_forms.py:222 +msgid "Site/Cluster" +msgstr "" + +#: virtualization/models/clusters.py:25 +msgid "cluster type" +msgstr "" + +#: virtualization/models/clusters.py:26 +msgid "cluster types" +msgstr "" + +#: virtualization/models/clusters.py:45 +msgid "cluster group" +msgstr "" + +#: virtualization/models/clusters.py:46 +msgid "cluster groups" +msgstr "" + +#: virtualization/models/clusters.py:121 +msgid "cluster" +msgstr "" + +#: virtualization/models/clusters.py:122 +msgid "clusters" +msgstr "" + +#: virtualization/models/clusters.py:141 +#, python-brace-format +msgid "" +"{count} devices are assigned as hosts for this cluster but are not in site " +"{site}" +msgstr "" + +#: virtualization/models/virtualmachines.py:120 +msgid "memory (MB)" +msgstr "" + +#: virtualization/models/virtualmachines.py:125 +msgid "disk (GB)" +msgstr "" + +#: virtualization/models/virtualmachines.py:154 +msgid "Virtual machine name must be unique per cluster." +msgstr "" + +#: virtualization/models/virtualmachines.py:157 +msgid "virtual machine" +msgstr "" + +#: virtualization/models/virtualmachines.py:158 +msgid "virtual machines" +msgstr "" + +#: virtualization/models/virtualmachines.py:172 +msgid "A virtual machine must be assigned to a site and/or cluster." +msgstr "" + +#: virtualization/models/virtualmachines.py:179 +#, python-brace-format +msgid "The selected cluster ({cluster}) is not assigned to this site ({site})." +msgstr "" + +#: virtualization/models/virtualmachines.py:186 +msgid "Must specify a cluster when assigning a host device." +msgstr "" + +#: virtualization/models/virtualmachines.py:191 +#, python-brace-format +msgid "" +"The selected device ({device}) is not assigned to this cluster ({cluster})." +msgstr "" + +#: virtualization/models/virtualmachines.py:204 +#, python-brace-format +msgid "Must be an IPv{family} address. ({ip} is an IPv{version} address.)" +msgstr "" + +#: virtualization/models/virtualmachines.py:213 +#, python-brace-format +msgid "The specified IP address ({ip}) is not assigned to this VM." +msgstr "" + +#: virtualization/models/virtualmachines.py:331 +#, python-brace-format +msgid "" +"The selected parent interface ({parent}) belongs to a different virtual " +"machine ({virtual_machine})." +msgstr "" + +#: virtualization/models/virtualmachines.py:346 +#, python-brace-format +msgid "" +"The selected bridge interface ({bridge}) belongs to a different virtual " +"machine ({virtual_machine})." +msgstr "" + +#: virtualization/models/virtualmachines.py:357 +#, python-brace-format +msgid "" +"The untagged VLAN ({untagged_vlan}) must belong to the same site as the " +"interface's parent virtual machine, or it must be global." +msgstr "" + +#: wireless/choices.py:11 +msgid "Access point" +msgstr "" + +#: wireless/choices.py:12 +msgid "Station" +msgstr "" + +#: wireless/choices.py:467 +msgid "Open" +msgstr "" + +#: wireless/choices.py:469 +msgid "WPA Personal (PSK)" +msgstr "" + +#: wireless/choices.py:470 +msgid "WPA Enterprise" +msgstr "" + +#: wireless/forms/bulk_edit.py:72 wireless/forms/bulk_edit.py:119 +#: wireless/forms/bulk_import.py:68 wireless/forms/bulk_import.py:71 +#: wireless/forms/bulk_import.py:110 wireless/forms/bulk_import.py:113 +#: wireless/forms/filtersets.py:58 wireless/forms/filtersets.py:92 +msgid "Authentication cipher" +msgstr "" + +#: wireless/forms/bulk_edit.py:78 wireless/forms/bulk_edit.py:125 +#: wireless/forms/filtersets.py:63 wireless/forms/filtersets.py:97 +msgid "Pre-shared key" +msgstr "" + +#: wireless/forms/bulk_import.py:52 +msgid "Bridged VLAN" +msgstr "" + +#: wireless/forms/bulk_import.py:89 wireless/tables/wirelesslink.py:27 +msgid "Interface A" +msgstr "" + +#: wireless/forms/bulk_import.py:93 wireless/tables/wirelesslink.py:36 +msgid "Interface B" +msgstr "" + +#: wireless/forms/model_forms.py:158 +msgid "Side B" +msgstr "" + +#: wireless/models.py:30 +msgid "authentication cipher" +msgstr "" + +#: wireless/models.py:38 +msgid "pre-shared key" +msgstr "" + +#: wireless/models.py:68 +msgid "wireless LAN group" +msgstr "" + +#: wireless/models.py:69 +msgid "wireless LAN groups" +msgstr "" + +#: wireless/models.py:115 +msgid "wireless LAN" +msgstr "" + +#: wireless/models.py:143 +msgid "interface A" +msgstr "" + +#: wireless/models.py:150 +msgid "interface B" +msgstr "" + +#: wireless/models.py:198 +msgid "wireless link" +msgstr "" + +#: wireless/models.py:199 +msgid "wireless links" +msgstr "" + +#: wireless/models.py:216 wireless/models.py:222 +#, python-brace-format +msgid "{type} is not a wireless interface." +msgstr "" diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 1c3233f87..b0a43ef22 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -386,5 +386,5 @@ class ObjectPermissionForm(BootstrapMixin, forms.ModelForm): model.objects.filter(qs_filter_from_constraints(constraints, tokens)).exists() except FieldError as e: raise forms.ValidationError({ - 'constraints': _('Invalid filter for {model}: {e}').format(model=model, e=e) + 'constraints': _('Invalid filter for {model}: {error}').format(model=model, error=e) }) diff --git a/netbox/users/models.py b/netbox/users/models.py index 80fd0dd09..1f8772704 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -169,7 +169,7 @@ class UserConfig(models.Model): elif key in d: err_path = '.'.join(path.split('.')[:i + 1]) raise TypeError( - _("Key '{err_path}' is a leaf node; cannot assign new keys").format(err_path=err_path) + _("Key '{path}' is a leaf node; cannot assign new keys").format(path=err_path) ) else: d = d.setdefault(key, {}) diff --git a/netbox/extras/templatetags/plugins.py b/netbox/utilities/templatetags/plugins.py similarity index 98% rename from netbox/extras/templatetags/plugins.py rename to netbox/utilities/templatetags/plugins.py index 560d15e01..c429bed5f 100644 --- a/netbox/extras/templatetags/plugins.py +++ b/netbox/utilities/templatetags/plugins.py @@ -2,7 +2,7 @@ from django import template as template_ from django.conf import settings from django.utils.safestring import mark_safe -from extras.plugins import PluginTemplateExtension +from netbox.plugins import PluginTemplateExtension from netbox.registry import registry register = template_.Library() diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 9524e242c..feb28c2d8 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -19,9 +19,9 @@ from jinja2.sandbox import SandboxedEnvironment from mptt.models import MPTTModel from dcim.choices import CableLengthUnitChoices, WeightUnitChoices -from extras.plugins import PluginConfig from extras.utils import is_taggable from netbox.config import get_config +from netbox.plugins import PluginConfig from urllib.parse import urlencode from utilities.constants import HTTP_REQUEST_META_SAFE_COPY diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index 21dbc895a..73d4ca841 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -151,8 +151,12 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form): for device in self.cleaned_data.get('devices', []): if device.site != self.cluster.site: raise ValidationError({ - 'devices': _("{} belongs to a different site ({}) than the cluster ({})").format( - device, device.site, self.cluster.site + 'devices': _( + "{device} belongs to a different site ({device_site}) than the cluster ({cluster_site})" + ).format( + device=device, + device_site=device.site, + cluster_site=self.cluster.site ) }) diff --git a/netbox/virtualization/models/clusters.py b/netbox/virtualization/models/clusters.py index 6c8fd0c4b..f8acc4c36 100644 --- a/netbox/virtualization/models/clusters.py +++ b/netbox/virtualization/models/clusters.py @@ -135,10 +135,9 @@ class Cluster(ContactsMixin, PrimaryModel): # If the Cluster is assigned to a Site, verify that all host Devices belong to that Site. if self.pk and self.site: - nonsite_devices = Device.objects.filter(cluster=self).exclude(site=self.site).count() - if nonsite_devices: + if nonsite_devices := Device.objects.filter(cluster=self).exclude(site=self.site).count(): raise ValidationError({ - 'site': _("{} devices are assigned as hosts for this cluster but are not in site {}").format( - nonsite_devices, self.site - ) + 'site': _( + "{count} devices are assigned as hosts for this cluster but are not in site {site}" + ).format(count=nonsite_devices, site=self.site) }) diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 173d7047b..798d1fc4d 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -16,6 +16,7 @@ from dcim.tables import DeviceTable from extras.views import ObjectConfigContextView from ipam.models import IPAddress from ipam.tables import InterfaceVLANTable +from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.views import generic from tenancy.views import ObjectContactsView from utilities.utils import count_related @@ -199,13 +200,13 @@ class ClusterDevicesView(generic.ObjectChildrenView): table = DeviceTable filterset = DeviceFilterSet template_name = 'virtualization/cluster/devices.html' - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_remove_devices') - action_perms = defaultdict(set, **{ + actions = { 'add': {'add'}, 'import': {'add'}, + 'export': {'view'}, 'bulk_edit': {'change'}, 'bulk_remove_devices': {'change'}, - }) + } tab = ViewTab( label=_('Devices'), badge=lambda obj: obj.devices.count(), @@ -359,20 +360,16 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView): table = tables.VirtualMachineVMInterfaceTable filterset = filtersets.VMInterfaceFilterSet template_name = 'virtualization/virtualmachine/interfaces.html' + actions = { + **DEFAULT_ACTION_PERMISSIONS, + 'bulk_rename': {'change'}, + } tab = ViewTab( label=_('Interfaces'), badge=lambda obj: obj.interface_count, permission='virtualization.view_vminterface', weight=500 ) - actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') - action_perms = defaultdict(set, **{ - 'add': {'add'}, - 'import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, - 'bulk_rename': {'change'}, - }) def get_children(self, request, parent): return parent.interfaces.restrict(request.user, 'view').prefetch_related( diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index e8e48eef8..0b114f85f 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -213,14 +213,14 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): if self.interface_a.type not in WIRELESS_IFACE_TYPES: raise ValidationError({ 'interface_a': _( - "{type_display} is not a wireless interface." - ).format(type_display=self.interface_a.get_type_display()) + "{type} is not a wireless interface." + ).format(type=self.interface_a.get_type_display()) }) if self.interface_b.type not in WIRELESS_IFACE_TYPES: raise ValidationError({ 'interface_a': _( - "{type_display} is not a wireless interface." - ).format(type_display=self.interface_b.get_type_display()) + "{type} is not a wireless interface." + ).format(type=self.interface_b.get_type_display()) }) def save(self, *args, **kwargs):