From 8e1c2ecd92129a443d52c3d1c94608d1446135e6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 May 2024 17:26:19 -0400 Subject: [PATCH] Closes #15915: Replace plugins list with an overall system status view (#15950) * Replace plugins list with an overall system status view * Enable export of system status data --- netbox/core/tables/plugins.py | 2 +- netbox/core/urls.py | 7 +- netbox/core/views.py | 85 ++++++++--- netbox/netbox/navigation/menu.py | 24 +--- netbox/templates/core/configrevision.html | 159 +-------------------- netbox/templates/core/inc/config_data.html | 149 +++++++++++++++++++ netbox/templates/core/plugin_list.html | 36 ----- netbox/templates/core/system.html | 92 ++++++++++++ 8 files changed, 315 insertions(+), 239 deletions(-) create mode 100644 netbox/templates/core/inc/config_data.html delete mode 100644 netbox/templates/core/plugin_list.html create mode 100644 netbox/templates/core/system.html diff --git a/netbox/core/tables/plugins.py b/netbox/core/tables/plugins.py index 2e3c0a991..21e90cd6b 100644 --- a/netbox/core/tables/plugins.py +++ b/netbox/core/tables/plugins.py @@ -35,5 +35,5 @@ class PluginTable(BaseTable): 'name', 'version', 'package', 'author', 'author_email', 'description', ) default_columns = ( - 'name', 'version', 'package', 'author', 'author_email', 'description', + 'name', 'version', 'package', 'description', ) diff --git a/netbox/core/urls.py b/netbox/core/urls.py index bac2eed37..59eead615 100644 --- a/netbox/core/urls.py +++ b/netbox/core/urls.py @@ -43,9 +43,6 @@ urlpatterns = ( path('config-revisions//restore/', views.ConfigRevisionRestoreView.as_view(), name='configrevision_restore'), path('config-revisions//', include(get_model_urls('core', 'configrevision'))), - # Configuration - path('config/', views.ConfigView.as_view(), name='config'), - - # Plugins - path('plugins/', views.PluginListView.as_view(), name='plugin_list'), + # System + path('system/', views.SystemView.as_view(), name='system'), ) diff --git a/netbox/core/views.py b/netbox/core/views.py index b19ab207b..5a65c5755 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -1,14 +1,19 @@ +import json +import platform + +from django import __version__ as DJANGO_VERSION from django.apps import apps from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import UserPassesTestMixin from django.core.cache import cache -from django.http import HttpResponseForbidden, Http404 +from django.db import connection, ProgrammingError +from django.http import HttpResponse, HttpResponseForbidden, Http404 from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.views.generic import View -from django_rq.queues import get_queue_by_index, get_redis_connection +from django_rq.queues import get_connection, get_queue_by_index, get_redis_connection from django_rq.settings import QUEUES_MAP, QUEUES_LIST from django_rq.utils import get_jobs, get_statistics, stop_jobs from rq import requeue_job @@ -175,20 +180,6 @@ class JobBulkDeleteView(generic.BulkDeleteView): # Config Revisions # -class ConfigView(generic.ObjectView): - queryset = ConfigRevision.objects.all() - - def get_object(self, **kwargs): - revision_id = cache.get('config_version') - try: - return ConfigRevision.objects.get(pk=revision_id) - except ConfigRevision.DoesNotExist: - # Fall back to using the active config data if no record is found - return ConfigRevision( - data=get_config().defaults - ) - - class ConfigRevisionListView(generic.ObjectListView): queryset = ConfigRevision.objects.all() filterset = filtersets.ConfigRevisionFilterSet @@ -527,21 +518,69 @@ class WorkerView(BaseRQView): # Plugins # -class PluginListView(UserPassesTestMixin, View): +class SystemView(UserPassesTestMixin, View): def test_func(self): return self.request.user.is_staff def get(self, request): + + # System stats + psql_version = db_name = db_size = None + try: + with connection.cursor() as cursor: + cursor.execute("SELECT version()") + psql_version = cursor.fetchone()[0] + psql_version = psql_version.split('(')[0].strip() + cursor.execute("SELECT current_database()") + db_name = cursor.fetchone()[0] + cursor.execute(f"SELECT pg_size_pretty(pg_database_size('{db_name}'))") + db_size = cursor.fetchone()[0] + except (ProgrammingError, IndexError): + pass + stats = { + 'netbox_version': settings.VERSION, + 'django_version': DJANGO_VERSION, + 'python_version': platform.python_version(), + 'postgresql_version': psql_version, + 'database_name': db_name, + 'database_size': db_size, + 'rq_worker_count': Worker.count(get_connection('default')), + } + + # Plugins plugins = [ # Look up app config by package name apps.get_app_config(plugin.rsplit('.', 1)[-1]) for plugin in settings.PLUGINS ] - table = tables.PluginTable(plugins, user=request.user) - table.configure(request) - return render(request, 'core/plugin_list.html', { - 'plugins': plugins, - 'active_tab': 'api-tokens', - 'table': table, + # Configuration + try: + config = ConfigRevision.objects.get(pk=cache.get('config_version')) + except ConfigRevision.DoesNotExist: + # Fall back to using the active config data if no record is found + config = ConfigRevision(data=get_config().defaults) + + # Raw data export + if 'export' in request.GET: + data = { + **stats, + 'plugins': { + plugin.name: plugin.version for plugin in plugins + }, + 'config': { + k: config.data[k] for k in sorted(config.data) + }, + } + response = HttpResponse(json.dumps(data, indent=4), content_type='text/json') + response['Content-Disposition'] = 'attachment; filename="netbox.json"' + return response + + plugins_table = tables.PluginTable(plugins, orderable=False) + plugins_table.configure(request) + + return render(request, 'core/system.html', { + 'stats': stats, + 'plugins_table': plugins_table, + 'config': config, }) diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 2d7fc3248..6bd3fb822 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -420,27 +420,17 @@ ADMIN_MENU = Menu( ), ), ), - MenuGroup( - label=_('Configuration'), - items=( - MenuItem( - link='core:config', - link_text=_('Current Config'), - permissions=['core.view_configrevision'] - ), - MenuItem( - link='core:configrevision_list', - link_text=_('Config Revisions'), - permissions=['core.view_configrevision'] - ), - ), - ), MenuGroup( label=_('System'), items=( MenuItem( - link='core:plugin_list', - link_text=_('Plugins') + link='core:system', + link_text=_('System') + ), + MenuItem( + link='core:configrevision_list', + link_text=_('Configuration History'), + permissions=['core.view_configrevision'] ), MenuItem( link='core:background_queue_list', diff --git a/netbox/templates/core/configrevision.html b/netbox/templates/core/configrevision.html index 1be674ab4..0aa7b3f05 100644 --- a/netbox/templates/core/configrevision.html +++ b/netbox/templates/core/configrevision.html @@ -32,163 +32,8 @@
-
{% trans "Rack Elevations" %}
- - - - - - - - - -
{% trans "Default unit height" %}{{ object.data.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT }}
{% trans "Default unit width" %}{{ object.data.RACK_ELEVATION_DEFAULT_UNIT_WIDTH }}
-
- -
-
{% trans "Power Feeds" %}
- - - - - - - - - - - - - -
{% trans "Default voltage" %}{{ object.data.POWERFEED_DEFAULT_VOLTAGE }}
{% trans "Default amperage" %}{{ object.data.POWERFEED_DEFAULT_AMPERAGE }}
{% trans "Default max utilization" %}{{ object.data.POWERFEED_DEFAULT_MAX_UTILIZATION }}
-
- -
-
{% trans "IPAM" %}
- - - - - - - - - -
{% trans "Enforce global unique" %}{{ object.data.ENFORCE_GLOBAL_UNIQUE }}
{% trans "Prefer IPv4" %}{{ object.data.PREFER_IPV4 }}
-
- -
-
{% trans "Security" %}
- - - - - -
{% trans "Allowed URL schemes" %}{{ object.data.ALLOWED_URL_SCHEMES|join:", "|placeholder }}
-
- -
-
{% trans "Banners" %}
- - - - - - - - - - - - - - - - - -
{% trans "Login banner" %}{{ object.data.BANNER_LOGIN }}
{% trans "Maintenance banner" %}{{ object.data.BANNER_MAINTENANCE }}
{% trans "Top banner" %}{{ object.data.BANNER_TOP }}
{% trans "Bottom banner" %}{{ object.data.BANNER_BOTTOM }}
-
- -
-
{% trans "Pagination" %}
- - - - - - - - - -
{% trans "Paginate count" %}{{ object.data.PAGINATE_COUNT }}
{% trans "Max page size" %}{{ object.data.MAX_PAGE_SIZE }}
-
- -
-
{% trans "Validation" %}
- - - - {% if object.data.CUSTOM_VALIDATORS %} - - {% else %} - - {% endif %} - - - - {% if object.data.PROTECTION_RULES %} - - {% else %} - - {% endif %} - -
{% trans "Custom validators" %} -
{{ object.data.CUSTOM_VALIDATORS|json }}
-
{{ ''|placeholder }}
{% trans "Protection rules" %} -
{{ object.data.PROTECTION_RULES|json }}
-
{{ ''|placeholder }}
-
- -
-
{% trans "User Preferences" %}
- - - - {% if object.data.DEFAULT_USER_PREFERENCES %} - - {% else %} - - {% endif %} - -
{% trans "Default user preferences" %} -
{{ object.data.DEFAULT_USER_PREFERENCES|json }}
-
{{ ''|placeholder }}
-
- -
-
{% trans "Miscellaneous" %}
- - - - - - - - - - - - - - - - - - - - - -
{% trans "Maintenance mode" %}{{ object.data.MAINTENANCE_MODE }}
{% trans "GraphQL enabled" %}{{ object.data.GRAPHQL_ENABLED }}
{% trans "Changelog retention" %}{{ object.data.CHANGELOG_RETENTION }}
{% trans "Job retention" %}{{ object.data.JOB_RETENTION }}
{% trans "Maps URL" %}{{ object.data.MAPS_URL }}
+
{% trans "Configuration Data" %}
+ {% include 'core/inc/config_data.html' with config=config.data %}
diff --git a/netbox/templates/core/inc/config_data.html b/netbox/templates/core/inc/config_data.html new file mode 100644 index 000000000..8305b5540 --- /dev/null +++ b/netbox/templates/core/inc/config_data.html @@ -0,0 +1,149 @@ +{% load i18n %} + + + + {# Rack elevations #} + + + + + + + + + + + + + {# Power feeds #} + + + + + + + + + + + + + + + + + {# IPAM #} + + + + + + + + + + + + + {# Security #} + + + + + + + + + {# Banners #} + + + + + + + + + + + + + + + + + + + + + {# Pagination #} + + + + + + + + + + + + + {# Validation #} + + + + + + {% if config.CUSTOM_VALIDATORS %} + + {% else %} + + {% endif %} + + + + {% if config.PROTECTION_RULES %} + + {% else %} + + {% endif %} + + + {# User Preferences #} + + + + + + {% if config.DEFAULT_USER_PREFERENCES %} + + {% else %} + + {% endif %} + + + {# Miscellaneous #} + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "Rack elevations" %}
{% trans "Default unit height" %}{{ config.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT }}
{% trans "Default unit width" %}{{ config.RACK_ELEVATION_DEFAULT_UNIT_WIDTH }}
{% trans "Power feeds" %}
{% trans "Default voltage" %}{{ config.POWERFEED_DEFAULT_VOLTAGE }}
{% trans "Default amperage" %}{{ config.POWERFEED_DEFAULT_AMPERAGE }}
{% trans "Default max utilization" %}{{ config.POWERFEED_DEFAULT_MAX_UTILIZATION }}
{% trans "IPAM" %}
{% trans "Enforce global unique" %}{% checkmark config.ENFORCE_GLOBAL_UNIQUE %}
{% trans "Prefer IPv4" %}{% checkmark config.PREFER_IPV4 %}
{% trans "Security" %}
{% trans "Allowed URL schemes" %}{{ config.ALLOWED_URL_SCHEMES|join:", "|placeholder }}
{% trans "Banners" %}
{% trans "Login banner" %}{{ config.BANNER_LOGIN|placeholder }}
{% trans "Maintenance banner" %}{{ config.BANNER_MAINTENANCE|placeholder }}
{% trans "Top banner" %}{{ config.BANNER_TOP|placeholder }}
{% trans "Bottom banner" %}{{ config.BANNER_BOTTOM|placeholder }}
{% trans "Pagination" %}
{% trans "Paginate count" %}{{ config.PAGINATE_COUNT }}
{% trans "Max page size" %}{{ config.MAX_PAGE_SIZE }}
{% trans "Validation" %}
{% trans "Custom validators" %}
{{ config.CUSTOM_VALIDATORS|json }}
{{ ''|placeholder }}
{% trans "Protection rules" %}
{{ config.PROTECTION_RULES|json }}
{{ ''|placeholder }}
{% trans "User preferences" %}
{% trans "Default preferences" %}
{{ config.DEFAULT_USER_PREFERENCES|json }}
{{ ''|placeholder }}
{% trans "Miscellaneous" %}
{% trans "Maintenance mode" %}{% checkmark config.MAINTENANCE_MODE %}
{% trans "GraphQL enabled" %}{% checkmark config.GRAPHQL_ENABLED %}
{% trans "Changelog retention" %}{{ config.CHANGELOG_RETENTION }}
{% trans "Job retention" %}{{ config.JOB_RETENTION }}
{% trans "Maps URL" %}{{ config.MAPS_URL }}
diff --git a/netbox/templates/core/plugin_list.html b/netbox/templates/core/plugin_list.html deleted file mode 100644 index 42f0b7156..000000000 --- a/netbox/templates/core/plugin_list.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'generic/_base.html' %} -{% load buttons %} -{% load helpers %} -{% load i18n %} -{% load render_table from django_tables2 %} - -{% block title %}{% trans "Plugins" %}{% endblock %} - -{% block tabs %} - -{% endblock tabs %} - -{% block content %} -
-
- {# Table configuration button #} -
- -
-
-
- -
- {% render_table table %} -
-{% endblock content %} - -{% block modals %} - {% table_config_form table table_name="ObjectTable" %} -{% endblock modals %} diff --git a/netbox/templates/core/system.html b/netbox/templates/core/system.html new file mode 100644 index 000000000..aa1294c0b --- /dev/null +++ b/netbox/templates/core/system.html @@ -0,0 +1,92 @@ +{% extends 'generic/_base.html' %} +{% load buttons %} +{% load helpers %} +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block title %}{% trans "System" %}{% endblock %} + +{% block controls %} + + {% trans "Export" %} + +{% endblock controls %} + +{% block tabs %} + +{% endblock tabs %} + +{% block content %} + {# System status #} +
+
+
+
{% trans "System Status" %}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "NetBox version" %}{{ stats.netbox_version }}
{% trans "Django version" %}{{ stats.django_version }}
{% trans "PotsgreSQL version" %}{{ stats.postgresql_version }}
{% trans "Database name" %}{{ stats.database_name }}
{% trans "Database size" %} + {% if stats.database_size %} + {{ stats.database_size }} + {% else %} + {% trans "Unavailable" %} + {% endif %} +
{% trans "RQ workers" %} + {{ stats.rq_worker_count }} + ({% trans "default queue" %}) +
{% trans "System time" %}{% now 'Y-m-d H:i:s T' %}
+
+
+
+ + {# Plugins #} +
+
+
+
{% trans "Plugins" %}
+ {% render_table plugins_table %} +
+
+
+ + {# Configuration #} +
+
+
+
{% trans "Current Configuration" %}
+ {% include 'core/inc/config_data.html' with config=config.data %} +
+ +
+
+{% endblock content %}