Skip to content

Commit

Permalink
Closes #15915: Replace plugins list with an overall system status view (
Browse files Browse the repository at this point in the history
#15950)

* Replace plugins list with an overall system status view

* Enable export of system status data
  • Loading branch information
jeremystretch authored May 3, 2024
1 parent a9b311b commit 8e1c2ec
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 236 deletions.
2 changes: 1 addition & 1 deletion netbox/core/tables/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ class Meta(BaseTable.Meta):
'name', 'version', 'package', 'author', 'author_email', 'description',
)
default_columns = (
'name', 'version', 'package', 'author', 'author_email', 'description',
'name', 'version', 'package', 'description',
)
7 changes: 2 additions & 5 deletions netbox/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@
path('config-revisions/<int:pk>/restore/', views.ConfigRevisionRestoreView.as_view(), name='configrevision_restore'),
path('config-revisions/<int:pk>/', 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'),
)
85 changes: 62 additions & 23 deletions netbox/core/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -527,21 +518,69 @@ def get(self, request, key):
# 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,
})
18 changes: 4 additions & 14 deletions netbox/netbox/navigation/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,27 +421,17 @@
),
),
MenuGroup(
label=_('Configuration'),
label=_('System'),
items=(
MenuItem(
link='core:config',
link_text=_('Current Config'),
permissions=['core.view_configrevision']
link='core:system',
link_text=_('System')
),
MenuItem(
link='core:configrevision_list',
link_text=_('Config Revisions'),
link_text=_('Configuration History'),
permissions=['core.view_configrevision']
),
),
),
MenuGroup(
label=_('System'),
items=(
MenuItem(
link='core:plugin_list',
link_text=_('Plugins')
),
MenuItem(
link='core:background_queue_list',
link_text=_('Background Tasks')
Expand Down
159 changes: 2 additions & 157 deletions netbox/templates/core/configrevision.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,163 +32,8 @@
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">{% trans "Rack Elevations" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Default unit height" %}</th>
<td>{{ object.data.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT }}</td>
</tr>
<tr>
<th scope="row">{% trans "Default unit width" %}</th>
<td>{{ object.data.RACK_ELEVATION_DEFAULT_UNIT_WIDTH }}</td>
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "Power Feeds" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Default voltage" %}</th>
<td>{{ object.data.POWERFEED_DEFAULT_VOLTAGE }}</td>
</tr>
<tr>
<th scope="row">{% trans "Default amperage" %}</th>
<td>{{ object.data.POWERFEED_DEFAULT_AMPERAGE }}</td>
</tr>
<tr>
<th scope="row">{% trans "Default max utilization" %}</th>
<td>{{ object.data.POWERFEED_DEFAULT_MAX_UTILIZATION }}</td>
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "IPAM" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Enforce global unique" %}</th>
<td>{{ object.data.ENFORCE_GLOBAL_UNIQUE }}</td>
</tr>
<tr>
<th scope="row">{% trans "Prefer IPv4" %}</th>
<td>{{ object.data.PREFER_IPV4 }}</td>
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "Security" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Allowed URL schemes" %}</th>
<td>{{ object.data.ALLOWED_URL_SCHEMES|join:", "|placeholder }}</td>
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "Banners" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Login banner" %}</th>
<td>{{ object.data.BANNER_LOGIN }}</td>
</tr>
<tr>
<th scope="row">{% trans "Maintenance banner" %}</th>
<td>{{ object.data.BANNER_MAINTENANCE }}</td>
</tr>
<tr>
<th scope="row">{% trans "Top banner" %}</th>
<td>{{ object.data.BANNER_TOP }}</td>
</tr>
<tr>
<th scope="row">{% trans "Bottom banner" %}</th>
<td>{{ object.data.BANNER_BOTTOM }}</td>
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "Pagination" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Paginate count" %}</th>
<td>{{ object.data.PAGINATE_COUNT }}</td>
</tr>
<tr>
<th scope="row">{% trans "Max page size" %}</th>
<td>{{ object.data.MAX_PAGE_SIZE }}</td>
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "Validation" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Custom validators" %}</th>
{% if object.data.CUSTOM_VALIDATORS %}
<td class="font-monospace">
<pre>{{ object.data.CUSTOM_VALIDATORS|json }}</pre>
</td>
{% else %}
<td>{{ ''|placeholder }}</td>
{% endif %}
</tr>
<tr>
<th scope="row">{% trans "Protection rules" %}</th>
{% if object.data.PROTECTION_RULES %}
<td>
<pre>{{ object.data.PROTECTION_RULES|json }}</pre>
</td>
{% else %}
<td>{{ ''|placeholder }}</td>
{% endif %}
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "User Preferences" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Default user preferences" %}</th>
{% if object.data.DEFAULT_USER_PREFERENCES %}
<td>
<pre>{{ object.data.DEFAULT_USER_PREFERENCES|json }}</pre>
</td>
{% else %}
<td>{{ ''|placeholder }}</td>
{% endif %}
</tr>
</table>
</div>

<div class="card">
<h5 class="card-header">{% trans "Miscellaneous" %}</h5>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Maintenance mode" %}</th>
<td>{{ object.data.MAINTENANCE_MODE }}</td>
</tr>
<tr>
<th scope="row">{% trans "GraphQL enabled" %}</th>
<td>{{ object.data.GRAPHQL_ENABLED }}</td>
</tr>
<tr>
<th scope="row">{% trans "Changelog retention" %}</th>
<td>{{ object.data.CHANGELOG_RETENTION }}</td>
</tr>
<tr>
<th scope="row">{% trans "Job retention" %}</th>
<td>{{ object.data.JOB_RETENTION }}</td>
</tr>
<tr>
<th scope="row">{% trans "Maps URL" %}</th>
<td>{{ object.data.MAPS_URL }}</td>
</tr>
</table>
<h5 class="card-header">{% trans "Configuration Data" %}</h5>
{% include 'core/inc/config_data.html' with config=config.data %}
</div>

<div class="card">
Expand Down
Loading

0 comments on commit 8e1c2ec

Please sign in to comment.