mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Merge pull request #7778 from netbox-community/7775-dynamic-config
7775 dynamic config
This commit is contained in:
commit
c0653da736
@ -31,6 +31,41 @@ This defines custom content to be displayed on the login page above the login fo
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## CHANGELOG_RETENTION
|
||||||
|
|
||||||
|
Default: 90
|
||||||
|
|
||||||
|
The number of days to retain logged changes (object creations, updates, and deletions). Set this to `0` to retain
|
||||||
|
changes in the database indefinitely.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
If enabling indefinite changelog retention, it is recommended to periodically delete old entries. Otherwise, the database may eventually exceed capacity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CUSTOM_VALIDATORS
|
||||||
|
|
||||||
|
This is a mapping of models to [custom validators](../customization/custom-validation.md) that have been defined locally to enforce custom validation logic. An example is provided below:
|
||||||
|
|
||||||
|
```python
|
||||||
|
CUSTOM_VALIDATORS = {
|
||||||
|
"dcim.site": [
|
||||||
|
{
|
||||||
|
"name": {
|
||||||
|
"min_length": 5,
|
||||||
|
"max_length": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_plugin.validators.Validator1"
|
||||||
|
],
|
||||||
|
"dim.device": [
|
||||||
|
"my_plugin.validators.Validator1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ENFORCE_GLOBAL_UNIQUE
|
## ENFORCE_GLOBAL_UNIQUE
|
||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
@ -39,6 +74,14 @@ By default, NetBox will permit users to create duplicate prefixes and IP address
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## GRAPHQL_ENABLED
|
||||||
|
|
||||||
|
Default: True
|
||||||
|
|
||||||
|
Setting this to False will disable the GraphQL API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## MAINTENANCE_MODE
|
## MAINTENANCE_MODE
|
||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
|
@ -25,18 +25,6 @@ BASE_PATH = 'netbox/'
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CHANGELOG_RETENTION
|
|
||||||
|
|
||||||
Default: 90
|
|
||||||
|
|
||||||
The number of days to retain logged changes (object creations, updates, and deletions). Set this to `0` to retain
|
|
||||||
changes in the database indefinitely.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
If enabling indefinite changelog retention, it is recommended to periodically delete old entries. Otherwise, the database may eventually exceed capacity.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CORS_ORIGIN_ALLOW_ALL
|
## CORS_ORIGIN_ALLOW_ALL
|
||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
@ -61,22 +49,6 @@ CORS_ORIGIN_WHITELIST = [
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CUSTOM_VALIDATORS
|
|
||||||
|
|
||||||
This is a mapping of models to [custom validators](../customization/custom-validation.md) that have been defined locally to enforce custom validation logic. An example is provided below:
|
|
||||||
|
|
||||||
```python
|
|
||||||
CUSTOM_VALIDATORS = {
|
|
||||||
'dcim.site': (
|
|
||||||
Validator1,
|
|
||||||
Validator2,
|
|
||||||
Validator3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## DEBUG
|
## DEBUG
|
||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
@ -168,14 +140,6 @@ EXEMPT_VIEW_PERMISSIONS = ['*']
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## GRAPHQL_ENABLED
|
|
||||||
|
|
||||||
Default: True
|
|
||||||
|
|
||||||
Setting this to False will disable the GraphQL API.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## HTTP_PROXIES
|
## HTTP_PROXIES
|
||||||
|
|
||||||
Default: None
|
Default: None
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#7619](https://github.com/netbox-community/netbox/issues/7619) - Permit custom validation rules to be defined as plain data or dotted path to class
|
* [#7619](https://github.com/netbox-community/netbox/issues/7619) - Permit custom validation rules to be defined as plain data or dotted path to class
|
||||||
|
* [#7775](https://github.com/netbox-community/netbox/issues/7775) - Enable dynamic config for `CHANGELOG_RETENTION`, `CUSTOM_VALIDATORS`, and `GRAPHQL_ENABLED`
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -27,11 +27,14 @@ class ConfigRevisionAdmin(admin.ModelAdmin):
|
|||||||
('Pagination', {
|
('Pagination', {
|
||||||
'fields': ('PAGINATE_COUNT', 'MAX_PAGE_SIZE'),
|
'fields': ('PAGINATE_COUNT', 'MAX_PAGE_SIZE'),
|
||||||
}),
|
}),
|
||||||
|
('Validation', {
|
||||||
|
'fields': ('CUSTOM_VALIDATORS',),
|
||||||
|
}),
|
||||||
('NAPALM', {
|
('NAPALM', {
|
||||||
'fields': ('NAPALM_USERNAME', 'NAPALM_PASSWORD', 'NAPALM_TIMEOUT', 'NAPALM_ARGS'),
|
'fields': ('NAPALM_USERNAME', 'NAPALM_PASSWORD', 'NAPALM_TIMEOUT', 'NAPALM_ARGS'),
|
||||||
}),
|
}),
|
||||||
('Miscellaneous', {
|
('Miscellaneous', {
|
||||||
'fields': ('MAINTENANCE_MODE', 'MAPS_URL'),
|
'fields': ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'MAPS_URL'),
|
||||||
}),
|
}),
|
||||||
('Config Revision', {
|
('Config Revision', {
|
||||||
'fields': ('comment',),
|
'fields': ('comment',),
|
||||||
|
@ -10,12 +10,14 @@ from django.utils import timezone
|
|||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
from extras.models import ObjectChange
|
from extras.models import ObjectChange
|
||||||
|
from netbox.config import Config
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Perform nightly housekeeping tasks. (This command can be run at any time.)"
|
help = "Perform nightly housekeeping tasks. (This command can be run at any time.)"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
config = Config()
|
||||||
|
|
||||||
# Clear expired authentication sessions (essentially replicating the `clearsessions` command)
|
# Clear expired authentication sessions (essentially replicating the `clearsessions` command)
|
||||||
if options['verbosity']:
|
if options['verbosity']:
|
||||||
@ -37,10 +39,10 @@ class Command(BaseCommand):
|
|||||||
# Delete expired ObjectRecords
|
# Delete expired ObjectRecords
|
||||||
if options['verbosity']:
|
if options['verbosity']:
|
||||||
self.stdout.write("[*] Checking for expired changelog records")
|
self.stdout.write("[*] Checking for expired changelog records")
|
||||||
if settings.CHANGELOG_RETENTION:
|
if config.CHANGELOG_RETENTION:
|
||||||
cutoff = timezone.now() - timedelta(days=settings.CHANGELOG_RETENTION)
|
cutoff = timezone.now() - timedelta(days=config.CHANGELOG_RETENTION)
|
||||||
if options['verbosity'] >= 2:
|
if options['verbosity'] >= 2:
|
||||||
self.stdout.write(f"\tRetention period: {settings.CHANGELOG_RETENTION} days")
|
self.stdout.write(f"\tRetention period: {config.CHANGELOG_RETENTION} days")
|
||||||
self.stdout.write(f"\tCut-off time: {cutoff}")
|
self.stdout.write(f"\tCut-off time: {cutoff}")
|
||||||
expired_records = ObjectChange.objects.filter(time__lt=cutoff).count()
|
expired_records = ObjectChange.objects.filter(time__lt=cutoff).count()
|
||||||
if expired_records:
|
if expired_records:
|
||||||
@ -58,7 +60,7 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write("\tNo expired records found.", self.style.SUCCESS)
|
self.stdout.write("\tNo expired records found.", self.style.SUCCESS)
|
||||||
elif options['verbosity']:
|
elif options['verbosity']:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
f"\tSkipping: No retention period specified (CHANGELOG_RETENTION = {settings.CHANGELOG_RETENTION})"
|
f"\tSkipping: No retention period specified (CHANGELOG_RETENTION = {config.CHANGELOG_RETENTION})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for new releases (if enabled)
|
# Check for new releases (if enabled)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||||
from django.dispatch import receiver, Signal
|
from django.dispatch import receiver, Signal
|
||||||
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||||
|
|
||||||
from extras.validators import CustomValidator
|
from extras.validators import CustomValidator
|
||||||
|
from netbox.config import get_config
|
||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from .choices import ObjectChangeActionChoices
|
from .choices import ObjectChangeActionChoices
|
||||||
from .models import ConfigRevision, CustomField, ObjectChange
|
from .models import ConfigRevision, CustomField, ObjectChange
|
||||||
@ -159,8 +159,9 @@ m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_type
|
|||||||
|
|
||||||
@receiver(post_clean)
|
@receiver(post_clean)
|
||||||
def run_custom_validators(sender, instance, **kwargs):
|
def run_custom_validators(sender, instance, **kwargs):
|
||||||
|
config = get_config()
|
||||||
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
|
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
|
||||||
validators = settings.CUSTOM_VALIDATORS.get(model_name, [])
|
validators = config.CUSTOM_VALIDATORS.get(model_name, [])
|
||||||
|
|
||||||
for validator in validators:
|
for validator in validators:
|
||||||
|
|
||||||
|
@ -94,6 +94,15 @@ PARAMS = (
|
|||||||
field=forms.IntegerField
|
field=forms.IntegerField
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
ConfigParam(
|
||||||
|
name='CUSTOM_VALIDATORS',
|
||||||
|
label='Custom validators',
|
||||||
|
default={},
|
||||||
|
description="Custom validation rules (JSON)",
|
||||||
|
field=forms.JSONField
|
||||||
|
),
|
||||||
|
|
||||||
# NAPALM
|
# NAPALM
|
||||||
ConfigParam(
|
ConfigParam(
|
||||||
name='NAPALM_USERNAME',
|
name='NAPALM_USERNAME',
|
||||||
@ -130,6 +139,20 @@ PARAMS = (
|
|||||||
description="Enable maintenance mode",
|
description="Enable maintenance mode",
|
||||||
field=forms.BooleanField
|
field=forms.BooleanField
|
||||||
),
|
),
|
||||||
|
ConfigParam(
|
||||||
|
name='GRAPHQL_ENABLED',
|
||||||
|
label='GraphQL enabled',
|
||||||
|
default=True,
|
||||||
|
description="Enable the GraphQL API",
|
||||||
|
field=forms.BooleanField
|
||||||
|
),
|
||||||
|
ConfigParam(
|
||||||
|
name='CHANGELOG_RETENTION',
|
||||||
|
label='Changelog retention',
|
||||||
|
default=90,
|
||||||
|
description="Days to retain changelog history (set to zero for unlimited)",
|
||||||
|
field=forms.IntegerField
|
||||||
|
),
|
||||||
ConfigParam(
|
ConfigParam(
|
||||||
name='MAPS_URL',
|
name='MAPS_URL',
|
||||||
label='Maps URL',
|
label='Maps URL',
|
||||||
|
@ -76,9 +76,6 @@ ADMINS = [
|
|||||||
# BASE_PATH = 'netbox/'
|
# BASE_PATH = 'netbox/'
|
||||||
BASE_PATH = ''
|
BASE_PATH = ''
|
||||||
|
|
||||||
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
|
|
||||||
CHANGELOG_RETENTION = 90
|
|
||||||
|
|
||||||
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
|
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
|
||||||
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
|
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
|
||||||
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
|
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
|
||||||
@ -90,20 +87,6 @@ CORS_ORIGIN_REGEX_WHITELIST = [
|
|||||||
# r'^(https?://)?(\w+\.)?example\.com$',
|
# r'^(https?://)?(\w+\.)?example\.com$',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Specify any custom validators here, as a mapping of model to a list of validators classes. Validators should be
|
|
||||||
# instances of or inherit from CustomValidator.
|
|
||||||
# from extras.validators import CustomValidator
|
|
||||||
CUSTOM_VALIDATORS = {
|
|
||||||
# 'dcim.site': [
|
|
||||||
# CustomValidator({
|
|
||||||
# 'name': {
|
|
||||||
# 'min_length': 10,
|
|
||||||
# 'regex': r'\d{3}$',
|
|
||||||
# }
|
|
||||||
# })
|
|
||||||
# ],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
|
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
|
||||||
# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
|
# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
|
||||||
# on a production system.
|
# on a production system.
|
||||||
@ -129,9 +112,6 @@ EXEMPT_VIEW_PERMISSIONS = [
|
|||||||
# 'ipam.prefix',
|
# 'ipam.prefix',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Enable the GraphQL API
|
|
||||||
GRAPHQL_ENABLED = True
|
|
||||||
|
|
||||||
# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks).
|
# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks).
|
||||||
# HTTP_PROXIES = {
|
# HTTP_PROXIES = {
|
||||||
# 'http': 'http://10.10.1.10:3128',
|
# 'http': 'http://10.10.1.10:3128',
|
||||||
|
@ -6,6 +6,7 @@ from graphene_django.views import GraphQLView as GraphQLView_
|
|||||||
from rest_framework.exceptions import AuthenticationFailed
|
from rest_framework.exceptions import AuthenticationFailed
|
||||||
|
|
||||||
from netbox.api.authentication import TokenAuthentication
|
from netbox.api.authentication import TokenAuthentication
|
||||||
|
from netbox.config import get_config
|
||||||
|
|
||||||
|
|
||||||
class GraphQLView(GraphQLView_):
|
class GraphQLView(GraphQLView_):
|
||||||
@ -15,9 +16,10 @@ class GraphQLView(GraphQLView_):
|
|||||||
graphiql_template = 'graphiql.html'
|
graphiql_template = 'graphiql.html'
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
# Enforce GRAPHQL_ENABLED
|
# Enforce GRAPHQL_ENABLED
|
||||||
if not settings.GRAPHQL_ENABLED:
|
if not config.GRAPHQL_ENABLED:
|
||||||
return HttpResponseNotFound("The GraphQL API is not enabled.")
|
return HttpResponseNotFound("The GraphQL API is not enabled.")
|
||||||
|
|
||||||
# Attempt to authenticate the user using a DRF token, if provided
|
# Attempt to authenticate the user using a DRF token, if provided
|
||||||
|
@ -80,11 +80,9 @@ ADMINS = getattr(configuration, 'ADMINS', [])
|
|||||||
BASE_PATH = getattr(configuration, 'BASE_PATH', '')
|
BASE_PATH = getattr(configuration, 'BASE_PATH', '')
|
||||||
if BASE_PATH:
|
if BASE_PATH:
|
||||||
BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only
|
BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only
|
||||||
CHANGELOG_RETENTION = getattr(configuration, 'CHANGELOG_RETENTION', 90)
|
|
||||||
CORS_ORIGIN_ALLOW_ALL = getattr(configuration, 'CORS_ORIGIN_ALLOW_ALL', False)
|
CORS_ORIGIN_ALLOW_ALL = getattr(configuration, 'CORS_ORIGIN_ALLOW_ALL', False)
|
||||||
CORS_ORIGIN_REGEX_WHITELIST = getattr(configuration, 'CORS_ORIGIN_REGEX_WHITELIST', [])
|
CORS_ORIGIN_REGEX_WHITELIST = getattr(configuration, 'CORS_ORIGIN_REGEX_WHITELIST', [])
|
||||||
CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', [])
|
CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', [])
|
||||||
CUSTOM_VALIDATORS = getattr(configuration, 'CUSTOM_VALIDATORS', {})
|
|
||||||
DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y')
|
DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y')
|
||||||
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
||||||
DEBUG = getattr(configuration, 'DEBUG', False)
|
DEBUG = getattr(configuration, 'DEBUG', False)
|
||||||
@ -92,7 +90,6 @@ DEVELOPER = getattr(configuration, 'DEVELOPER', False)
|
|||||||
DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
|
DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
|
||||||
EMAIL = getattr(configuration, 'EMAIL', {})
|
EMAIL = getattr(configuration, 'EMAIL', {})
|
||||||
EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
|
EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
|
||||||
GRAPHQL_ENABLED = getattr(configuration, 'GRAPHQL_ENABLED', True)
|
|
||||||
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
|
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
|
||||||
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
|
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
|
||||||
LOGGING = getattr(configuration, 'LOGGING', {})
|
LOGGING = getattr(configuration, 'LOGGING', {})
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
{# GraphQL API #}
|
{# GraphQL API #}
|
||||||
{% if settings.GRAPHQL_ENABLED %}
|
{% if config.GRAPHQL_ENABLED %}
|
||||||
<a type="button" class="nav-link" href="{% url 'graphql' %}" target="_blank">
|
<a type="button" class="nav-link" href="{% url 'graphql' %}" target="_blank">
|
||||||
<i title="GraphQL API" class="mdi mdi-graphql text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
|
<i title="GraphQL API" class="mdi mdi-graphql text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
|
||||||
</a>
|
</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user