Closes #19773: Extend system view (#20078)

This commit is contained in:
Jeremy Stretch 2025-08-12 13:59:15 -04:00 committed by GitHub
parent bb57021197
commit 8238fda8ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 146 additions and 67 deletions

View File

@ -1,3 +1,4 @@
import json
import urllib.parse
import uuid
from datetime import datetime
@ -366,6 +367,11 @@ class SystemTestCase(TestCase):
# Test export
response = self.client.get(f"{reverse('core:system')}?export=true")
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertIn('netbox_release', data)
self.assertIn('plugins', data)
self.assertIn('config', data)
self.assertIn('objects', data)
def test_system_view_with_config_revision(self):
ConfigRevision.objects.create()

View File

@ -1,7 +1,7 @@
import json
import platform
from django import __version__ as DJANGO_VERSION
from django import __version__ as django_version
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin
@ -23,7 +23,7 @@ from rq.worker_registration import clean_worker_registry
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, requeue_rq_job, stop_rq_job
from netbox.config import get_config, PARAMS
from netbox.object_actions import AddObject, BulkDelete, BulkExport, DeleteObject
from netbox.registry import registry
from netbox.plugins.utils import get_installed_plugins
from netbox.views import generic
from netbox.views.generic.base import BaseObjectView
from netbox.views.generic.mixins import TableMixin
@ -546,7 +546,7 @@ class SystemView(UserPassesTestMixin, View):
def get(self, request):
# System stats
# System status
psql_version = db_name = db_size = None
try:
with connection.cursor() as cursor:
@ -561,7 +561,7 @@ class SystemView(UserPassesTestMixin, View):
pass
stats = {
'netbox_release': settings.RELEASE,
'django_version': DJANGO_VERSION,
'django_version': django_version,
'python_version': platform.python_version(),
'postgresql_version': psql_version,
'database_name': db_name,
@ -572,16 +572,28 @@ class SystemView(UserPassesTestMixin, View):
# Configuration
config = get_config()
# Plugins
plugins = get_installed_plugins()
# Object counts
objects = {}
for ot in ObjectType.objects.public().order_by('app_label', 'model'):
if model := ot.model_class():
objects[ot] = model.objects.count()
# Raw data export
if 'export' in request.GET:
stats['netbox_release'] = stats['netbox_release'].asdict()
params = [param.name for param in PARAMS]
data = {
**stats,
'plugins': registry['plugins']['installed'],
'plugins': plugins,
'config': {
k: getattr(config, k) for k in sorted(params)
},
'objects': {
f'{ot.app_label}.{ot.model}': count for ot, count in objects.items()
},
}
response = HttpResponse(json.dumps(data, cls=ConfigJSONEncoder, indent=4), content_type='text/json')
response['Content-Disposition'] = 'attachment; filename="netbox.json"'
@ -595,6 +607,8 @@ class SystemView(UserPassesTestMixin, View):
return render(request, 'core/system.html', {
'stats': stats,
'config': config,
'plugins': plugins,
'objects': objects,
})

View File

@ -8,20 +8,38 @@
{% block controls %}
<a href="?export=true" class="btn btn-purple">
<i class="mdi mdi-download"></i> {% trans "Export" %}
<i class="mdi mdi-download"></i> {% trans "Export All" %}
</a>
{% endblock controls %}
{% block tabs %}
<ul class="nav nav-tabs px-3">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" role="tab">{% trans "Status" %}</a>
<a class="nav-link active" id="status-tab" data-bs-toggle="tab" data-bs-target="#status-panel" type="button" role="tab" aria-selected="true">
{% trans "Status" %}
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="config-tab" data-bs-toggle="tab" data-bs-target="#config-panel" type="button" role="tab">
{% trans "Config" %}
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="plugins-tab" data-bs-toggle="tab" data-bs-target="#plugins-panel" type="button" role="tab">
{% trans "Plugins" %}
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="objects-tab" data-bs-toggle="tab" data-bs-target="#objects-panel" type="button" role="tab">
{% trans "Object Counts" %}
</a>
</li>
</ul>
{% endblock tabs %}
{% block content %}
{# System status #}
{# Status panel #}
<div class="tab-pane show active" id="status-panel" role="tabpanel" aria-labelledby="status-tab">
<div class="row mb-3">
<div class="col">
<div class="card">
@ -77,14 +95,55 @@
</div>
</div>
</div>
</div>
{# Configuration #}
{# Config panel #}
<div class="tab-pane" id="config-panel" role="tabpanel" aria-labelledby="config-tab">
<div class="row mb-3">
<div class="col col-md-12">
<div class="col">
<div class="card">
<h2 class="card-header">{% trans "Current Configuration" %}</h2>
{% include 'core/inc/config_data.html' %}
</div>
</div>
</div>
</div>
{# Plugins panel #}
<div class="tab-pane" id="plugins-panel" role="tabpanel" aria-labelledby="plugins-tab">
<div class="row mb-3">
<div class="col">
<div class="card">
<h2 class="card-header">{% trans "Installed Plugins" %}</h2>
<table class="table table-hover attr-table">
{% for plugin, version in plugins.items %}
<tr>
<td>{{ plugin }}</td>
<td>{{ version }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{# Objects panel #}
<div class="tab-pane" id="objects-panel" role="tabpanel" aria-labelledby="objects-tab">
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h2 class="card-header">{% trans "Object Counts" %}</h2>
<table class="table table-hover attr-table">
{% for object_type, count in objects.items %}
<tr{% if not count %} class="text-muted"{% endif %}>
<td>{{ object_type }}</td>
<td>{{ count }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{% endblock content %}