diff --git a/netbox/core/views.py b/netbox/core/views.py index e15745445..3d1e02b4d 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -26,7 +26,10 @@ from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, r from extras.ui.panels import CustomFieldsPanel, TagsPanel from netbox.config import PARAMS, get_config from netbox.object_actions import AddObject, BulkDelete, BulkExport, DeleteObject +from django.apps import apps as django_apps_registry +from netbox.plugins import PluginConfig from netbox.plugins.utils import get_installed_plugins +from netbox.registry import registry from netbox.ui import layout from netbox.ui.panels import ( CommentsPanel, @@ -744,19 +747,50 @@ class SystemView(UserPassesTestMixin, View): except ProgrammingError: pass - # Group tables by app prefix (e.g. "dcim", "ipam") + # Collect plugin app labels so their tables can be separated. + # Combine two sources: + # 1. PluginConfig subclasses in Django's app registry (directly registered plugins) + # 2. registry['plugins']['installed'] names (catches sub-apps registered via + # django_apps that use plain AppConfig instead of PluginConfig) + plugin_app_labels = { + app_config.label + for app_config in django_apps_registry.get_app_configs() + if isinstance(app_config, PluginConfig) + } + plugin_app_labels.update( + plugin_name.rsplit('.', 1)[-1] + for plugin_name in registry['plugins']['installed'] + ) + + # Group tables by app prefix (e.g. "dcim", "ipam"), plugins last. + # Sort plugin labels longest-first so a label like "netbox_branching" is + # matched before a shorter label that shares the same prefix. + _sorted_plugin_labels = sorted(plugin_app_labels, key=len, reverse=True) _groups = {} for table in db_schema: - prefix = table['name'].split('_')[0] if '_' in table['name'] else 'other' + matched_plugin = next( + (label for label in _sorted_plugin_labels if table['name'].startswith(label + '_')), + None, + ) + if matched_plugin: + prefix = matched_plugin + elif '_' in table['name']: + prefix = table['name'].split('_')[0] + else: + prefix = 'other' _groups.setdefault(prefix, []).append(table) - db_schema_groups = [ - { - 'name': name, - 'tables': tables, - 'index_count': sum(len(t['indexes']) for t in tables), - } - for name, tables in sorted(_groups.items()) - ] + db_schema_groups = sorted( + [ + { + 'name': name, + 'tables': tables, + 'index_count': sum(len(t['indexes']) for t in tables), + 'is_plugin': name in plugin_app_labels, + } + for name, tables in _groups.items() + ], + key=lambda g: (g['is_plugin'], g['name']), + ) db_schema_stats = { 'total_tables': len(db_schema), diff --git a/netbox/templates/core/system.html b/netbox/templates/core/system.html index 936ea6cc2..8dc897f2e 100644 --- a/netbox/templates/core/system.html +++ b/netbox/templates/core/system.html @@ -223,6 +223,7 @@ aria-expanded="false" aria-controls="db-group-body-{{ group.name }}"> {{ group.name }} + {% if group.is_plugin %}{% trans "plugin" %}{% endif %} {{ group.tables|length }} {% trans "tables" %} {{ group.index_count }} {% trans "indexes" %}