diff --git a/docs/administration/authentication/microsoft-azure-ad.md b/docs/administration/authentication/microsoft-azure-ad.md index ee24e8232..325ab7c3e 100644 --- a/docs/administration/authentication/microsoft-azure-ad.md +++ b/docs/administration/authentication/microsoft-azure-ad.md @@ -73,7 +73,7 @@ You should be redirected to Microsoft's authentication portal. Enter the usernam If successful, you will be redirected back to the NetBox UI, and will be logged in as the AD user. You can verify this by navigating to your profile (using the button at top right). -This user account has been replicated locally to NetBox, and can now be assigned groups and permissions within the NetBox admin UI. +This user account has been replicated locally to NetBox, and can now be assigned groups and permissions within NetBox. ## Troubleshooting diff --git a/docs/administration/authentication/okta.md b/docs/administration/authentication/okta.md index ff552d730..cb60c7189 100644 --- a/docs/administration/authentication/okta.md +++ b/docs/administration/authentication/okta.md @@ -67,4 +67,4 @@ You should be redirected to Okta's authentication portal. Enter the username/ema If successful, you will be redirected back to the NetBox UI, and will be logged in as the Okta user. You can verify this by navigating to your profile (using the button at top right). -This user account has been replicated locally to NetBox, and can now be assigned groups and permissions within the NetBox admin UI. +This user account has been replicated locally to NetBox, and can now be assigned groups and permissions within NetBox. diff --git a/docs/administration/authentication/overview.md b/docs/administration/authentication/overview.md index 8a8b8f60b..eb34883b8 100644 --- a/docs/administration/authentication/overview.md +++ b/docs/administration/authentication/overview.md @@ -4,7 +4,7 @@ Local user accounts and groups can be created in NetBox under the "Authentication and Authorization" section of the administrative user interface. This interface is available only to users with the "staff" permission enabled. -At a minimum, each user account must have a username and password set. User accounts may also denote a first name, last name, and email address. [Permissions](../permissions.md) may also be assigned to users and/or groups within the admin UI. +At a minimum, each user account must have a username and password set. User accounts may also denote a first name, last name, and email address. [Permissions](../permissions.md) may also be assigned to individual users and/or groups as needed. ## Remote Authentication diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 70466d029..6bb150c47 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -46,4 +46,4 @@ The configuration file may be modified at any time. However, the WSGI service (e $ sudo systemctl restart netbox ``` -Configuration parameters which are set via the admin UI (those listed under "dynamic settings") take effect immediately. +Configuration parameters which are set via the user interface (those listed under "dynamic settings") take effect immediately. diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 4d4ca189e..7e68bcee7 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -99,6 +99,14 @@ The maximum size (in bytes) of an incoming HTTP request (i.e. `GET` or `POST` da --- +## DJANGO_ADMIN_ENABLED + +Default: False + +Setting this to True installs the `django.contrib.admin` app and enables the [Django admin UI](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/). This may be necessary to support older plugins which do not integrate with the native NetBox interface. + +--- + ## ENFORCE_GLOBAL_UNIQUE !!! tip "Dynamic Configuration Parameter" diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index 0b1ed11df..388cbdb88 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -288,7 +288,7 @@ An IPv4 or IPv6 network with a mask. Returns a `netaddr.IPNetwork` object. Two a ## Running Custom Scripts !!! note - To run a custom script, a user must be assigned via permissions for `Extras > Script`, `Extras > ScriptModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action in the admin UI as shown below. + To run a custom script, a user must be assigned via permissions for `Extras > Script`, `Extras > ScriptModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action as shown below. ![Adding the run action to a permission](../media/admin_ui_run_permission.png) diff --git a/docs/customization/reports.md b/docs/customization/reports.md index a821c5da7..73ffe9538 100644 --- a/docs/customization/reports.md +++ b/docs/customization/reports.md @@ -132,7 +132,7 @@ Once you have created a report, it will appear in the reports list. Initially, r ## Running Reports !!! note - To run a report, a user must be assigned via permissions for `Extras > Report`, `Extras > ReportModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below. + To run a report, a user must be assigned via permissions for `Extras > Report`, `Extras > ReportModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action as shown below. ![Adding the run action to a permission](../media/admin_ui_run_permission.png) diff --git a/docs/features/event-rules.md b/docs/features/event-rules.md index 0e9535223..158dc111a 100644 --- a/docs/features/event-rules.md +++ b/docs/features/event-rules.md @@ -28,4 +28,4 @@ For more detail, see the reference documentation for NetBox's [conditional logic ## Event Rule Processing -When a change is detected, any resulting events are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing event(s) to be processed. The events are then extracted from the queue by the `rqworker` process. The current event queue and any failed events can be inspected in the admin UI under System > Background Tasks. +When a change is detected, any resulting events are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing event(s) to be processed. The events are then extracted from the queue by the `rqworker` process. The current event queue and any failed events can be inspected under System > Background Tasks. diff --git a/docs/integrations/webhooks.md b/docs/integrations/webhooks.md index 8913fd99c..7fd7c6e7f 100644 --- a/docs/integrations/webhooks.md +++ b/docs/integrations/webhooks.md @@ -73,9 +73,9 @@ If no body template is specified, the request body will be populated with a JSON ## Webhook Processing -Using [Event Rules](../features/event-rules.md), when a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected in the admin UI under System > Background Tasks. +Using [Event Rules](../features/event-rules.md), when a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected under System > Background Tasks. -A request is considered successful if the response has a 2XX status code; otherwise, the request is marked as having failed. Failed requests may be retried manually via the admin UI. +A request is considered successful if the response has a 2XX status code; otherwise, the request is marked as having failed. Failed requests may be requeued manually under System > Background Tasks. ## Troubleshooting @@ -108,4 +108,4 @@ Content-Type: application/x-www-form-urlencoded Note that `webhook_receiver` does not actually _do_ anything with the information received: It merely prints the request headers and body for inspection. -Now, when the NetBox webhook is triggered and processed, you should see its headers and content appear in the terminal where the webhook receiver is listening. If you don't, check that the `rqworker` process is running and that webhook events are being placed into the queue (visible under the NetBox admin UI). +Now, when the NetBox webhook is triggered and processed, you should see its headers and content appear in the terminal where the webhook receiver is listening. If you don't, check that the `rqworker` process is running and that webhook events are being placed into the queue. diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index cec05cabb..346cd89d2 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -39,6 +39,8 @@ REDIS = { SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' +DJANGO_ADMIN_ENABLED = True + DEFAULT_PERMISSIONS = {} LOGGING = { diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index bc7603fc4..8e40686c1 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -115,6 +115,7 @@ DEFAULT_PERMISSIONS = getattr(configuration, 'DEFAULT_PERMISSIONS', { 'users.delete_token': ({'user': '$user'},), }) DEVELOPER = getattr(configuration, 'DEVELOPER', False) +DJANGO_ADMIN_ENABLED = getattr(configuration, 'DJANGO_ADMIN_ENABLED', False) DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs')) EMAIL = getattr(configuration, 'EMAIL', {}) EVENTS_PIPELINE = getattr(configuration, 'EVENTS_PIPELINE', ( @@ -355,7 +356,6 @@ SERVER_EMAIL = EMAIL.get('FROM_EMAIL') # INSTALLED_APPS = [ - 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -393,6 +393,9 @@ INSTALLED_APPS = [ 'drf_spectacular_sidecar', ] +if DJANGO_ADMIN_ENABLED: + INSTALLED_APPS.insert(0, 'django.contrib.admin') + # Middleware MIDDLEWARE = [ 'graphiql_debug_toolbar.middleware.DebugToolbarMiddleware', diff --git a/netbox/netbox/tests/dummy_plugin/navigation.py b/netbox/netbox/tests/dummy_plugin/navigation.py index 4e7bb4be8..803cb67dc 100644 --- a/netbox/netbox/tests/dummy_plugin/navigation.py +++ b/netbox/netbox/tests/dummy_plugin/navigation.py @@ -4,23 +4,23 @@ from netbox.plugins.navigation import PluginMenu, PluginMenuButton, PluginMenuIt items = ( PluginMenuItem( - link='plugins:dummy_plugin:dummy_models', + link='plugins:dummy_plugin:dummy_model_list', link_text='Item 1', buttons=( PluginMenuButton( - link='admin:dummy_plugin_dummymodel_add', - title='Add a new dummy model', + link='plugins:dummy_plugin:dummy_model_add', + title='Button 1', icon_class='mdi mdi-plus-thick', ), PluginMenuButton( - link='admin:dummy_plugin_dummymodel_add', - title='Add a new dummy model', + link='plugins:dummy_plugin:dummy_model_add', + title='Button 2', icon_class='mdi mdi-plus-thick', ), ) ), PluginMenuItem( - link='plugins:dummy_plugin:dummy_models', + link='plugins:dummy_plugin:dummy_model_list', link_text='Item 2', ), ) diff --git a/netbox/netbox/tests/dummy_plugin/urls.py b/netbox/netbox/tests/dummy_plugin/urls.py index 053a7443e..9e383ffe2 100644 --- a/netbox/netbox/tests/dummy_plugin/urls.py +++ b/netbox/netbox/tests/dummy_plugin/urls.py @@ -4,5 +4,6 @@ from . import views urlpatterns = ( - path('models/', views.DummyModelsView.as_view(), name='dummy_models'), + path('models/', views.DummyModelsView.as_view(), name='dummy_model_list'), + path('models/add/', views.DummyModelAddView.as_view(), name='dummy_model_add'), ) diff --git a/netbox/netbox/tests/dummy_plugin/views.py b/netbox/netbox/tests/dummy_plugin/views.py index 03a83b585..f6cf6a5c5 100644 --- a/netbox/netbox/tests/dummy_plugin/views.py +++ b/netbox/netbox/tests/dummy_plugin/views.py @@ -1,3 +1,6 @@ +import random +import string + from django.http import HttpResponse from django.views.generic import View @@ -15,6 +18,20 @@ class DummyModelsView(View): return HttpResponse(f"Instances: {instance_count}") +class DummyModelAddView(View): + + def get(self, request): + return HttpResponse(f"Create an instance") + + def post(self, request): + instance = DummyModel( + name=''.join(random.choices(string.ascii_lowercase, k=8)), + number=random.randint(1, 100000) + ) + instance.save() + return HttpResponse(f"Instance created") + + @register_model_view(Site, 'extra', path='other-stuff') class ExtraCoreModelView(View): diff --git a/netbox/netbox/tests/test_plugins.py b/netbox/netbox/tests/test_plugins.py index 40bf8b0ea..f7aeb7a53 100644 --- a/netbox/netbox/tests/test_plugins.py +++ b/netbox/netbox/tests/test_plugins.py @@ -41,7 +41,7 @@ class PluginTest(TestCase): def test_views(self): # Test URL resolution - url = reverse('plugins:dummy_plugin:dummy_models') + url = reverse('plugins:dummy_plugin:dummy_model_list') self.assertEqual(url, '/plugins/dummy-plugin/models/') # Test GET request diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 38044a613..43cfc1d4f 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -11,7 +11,6 @@ from netbox.graphql.schema import schema from netbox.graphql.views import GraphQLView from netbox.plugins.urls import plugin_patterns, plugin_api_patterns from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx -from .admin import admin_site _patterns = [ @@ -70,26 +69,25 @@ _patterns = [ # Plugins path('plugins/', include((plugin_patterns, 'plugins'))), path('api/plugins/', include((plugin_api_patterns, 'plugins-api'))), - - # Admin - path('admin/', admin_site.urls), ] +# Django admin UI +if settings.DJANGO_ADMIN_ENABLED: + from .admin import admin_site + _patterns.append(path('admin/', admin_site.urls)) +# django-debug-toolbar if settings.DEBUG: import debug_toolbar - _patterns += [ - path('__debug__/', include(debug_toolbar.urls)), - ] + _patterns.append(path('__debug__/', include(debug_toolbar.urls))) +# Prometheus metrics if settings.METRICS_ENABLED: - _patterns += [ - path('', include('django_prometheus.urls')), - ] + _patterns.append(path('', include('django_prometheus.urls'))) # Prepend BASE_PATH urlpatterns = [ - path('{}'.format(settings.BASE_PATH), include(_patterns)) + path(settings.BASE_PATH, include(_patterns)) ] handler404 = 'netbox.views.errors.handler_404' diff --git a/netbox/templates/account/profile.html b/netbox/templates/account/profile.html index 07c701de2..fb6da891a 100644 --- a/netbox/templates/account/profile.html +++ b/netbox/templates/account/profile.html @@ -38,7 +38,7 @@ {% checkmark request.user.is_superuser %} - {% trans "Admin Access" %} + {% trans "Staff" %} {% checkmark request.user.is_staff %} diff --git a/netbox/templates/core/configrevision_restore.html b/netbox/templates/core/configrevision_restore.html index 884abafe8..0097f93e5 100644 --- a/netbox/templates/core/configrevision_restore.html +++ b/netbox/templates/core/configrevision_restore.html @@ -45,7 +45,11 @@ {{ param }} {{ current }} {{ new }} - {% if current != new %}*{% endif %} + + {% if current != new %} + + {% endif %} + {% endfor %} diff --git a/netbox/templates/inc/user_menu.html b/netbox/templates/inc/user_menu.html index 5beca96dc..95bc639c0 100644 --- a/netbox/templates/inc/user_menu.html +++ b/netbox/templates/inc/user_menu.html @@ -9,9 +9,9 @@