From ef5e10d360d81547a92b28224fa225c394c4aa91 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Fri, 19 Jan 2024 08:27:15 -0800 Subject: [PATCH] 14728 Move installed plugins list from admin UI to NetBox UI (#14768) * 14728 move plugins view from admin * 14728 move plugins view from admin * 14728 remove plugins view from admin * Update template for #12128 * 14728 review fixes * 14728 review fixes * 14728 review fixes * 14728 review fixes * 14728 configure table * Clean up table columns * Fix app config lookup for plugins referenced by dotted path * Move template; fix table display * Fix user table configuration * Remove nonfunctional quick search * Limit PluginListView to staff users --------- Co-authored-by: Jeremy Stretch --- netbox/core/tables/__init__.py | 1 + netbox/core/tables/plugins.py | 39 +++++++++++++ netbox/core/urls.py | 2 + netbox/core/views.py | 27 +++++++++ netbox/extras/views.py | 2 + netbox/netbox/navigation/menu.py | 10 ++++ netbox/netbox/plugins/urls.py | 3 - netbox/netbox/plugins/views.py | 11 ---- netbox/netbox/urls.py | 3 +- netbox/templates/core/plugin_list.html | 36 ++++++++++++ .../templates/extras/admin/plugins_list.html | 58 ------------------- 11 files changed, 118 insertions(+), 74 deletions(-) create mode 100644 netbox/core/tables/plugins.py create mode 100644 netbox/templates/core/plugin_list.html delete mode 100644 netbox/templates/extras/admin/plugins_list.html diff --git a/netbox/core/tables/__init__.py b/netbox/core/tables/__init__.py index 69f9d8a48..29dc7d85e 100644 --- a/netbox/core/tables/__init__.py +++ b/netbox/core/tables/__init__.py @@ -1,3 +1,4 @@ from .config import * from .data import * from .jobs import * +from .plugins import * diff --git a/netbox/core/tables/plugins.py b/netbox/core/tables/plugins.py new file mode 100644 index 000000000..2e3c0a991 --- /dev/null +++ b/netbox/core/tables/plugins.py @@ -0,0 +1,39 @@ +import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ +from netbox.tables import BaseTable + +__all__ = ( + 'PluginTable', +) + + +class PluginTable(BaseTable): + name = tables.Column( + accessor=tables.A('verbose_name'), + verbose_name=_('Name') + ) + version = tables.Column( + verbose_name=_('Version') + ) + package = tables.Column( + accessor=tables.A('name'), + verbose_name=_('Package') + ) + author = tables.Column( + verbose_name=_('Author') + ) + author_email = tables.Column( + verbose_name=_('Author Email') + ) + description = tables.Column( + verbose_name=_('Description') + ) + + class Meta(BaseTable.Meta): + empty_text = _('No plugins found') + fields = ( + 'name', 'version', 'package', 'author', 'author_email', 'description', + ) + default_columns = ( + 'name', 'version', 'package', 'author', 'author_email', 'description', + ) diff --git a/netbox/core/urls.py b/netbox/core/urls.py index 77c0d3194..3bb5cd24c 100644 --- a/netbox/core/urls.py +++ b/netbox/core/urls.py @@ -35,4 +35,6 @@ urlpatterns = ( # Configuration path('config/', views.ConfigView.as_view(), name='config'), + # Plugins + path('plugins/', views.PluginListView.as_view(), name='plugin_list'), ) diff --git a/netbox/core/views.py b/netbox/core/views.py index 537c33d9d..f81957927 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -1,4 +1,7 @@ +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 from django.shortcuts import get_object_or_404, redirect, render @@ -232,3 +235,27 @@ class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View): messages.success(request, f"Restored configuration revision #{pk}") return redirect(candidate_config.get_absolute_url()) + + +# +# Plugins +# + +class PluginListView(UserPassesTestMixin, View): + + def test_func(self): + return self.request.user.is_staff + + def get(self, request): + 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, + }) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index a3dd7f193..5a1fbf19e 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,3 +1,5 @@ +from django.apps import apps +from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 32b19ef62..e63947424 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -450,6 +450,16 @@ ADMIN_MENU = Menu( ), ), ), + MenuGroup( + label=_('Plugins'), + items=( + MenuItem( + link='core:plugin_list', + link_text=_('Plugins'), + staff_only=True + ), + ), + ), ), ) diff --git a/netbox/netbox/plugins/urls.py b/netbox/netbox/plugins/urls.py index 2f237f56a..075bda811 100644 --- a/netbox/netbox/plugins/urls.py +++ b/netbox/netbox/plugins/urls.py @@ -15,9 +15,6 @@ plugin_api_patterns = [ path('', views.PluginsAPIRootView.as_view(), name='api-root'), path('installed-plugins/', views.InstalledPluginsAPIView.as_view(), name='plugins-list') ] -plugin_admin_patterns = [ - path('installed-plugins/', staff_member_required(views.InstalledPluginsAdminView.as_view()), name='plugins_list') -] # Register base/API URL patterns for each plugin for plugin_path in settings.PLUGINS: diff --git a/netbox/netbox/plugins/views.py b/netbox/netbox/plugins/views.py index 5971f78ef..777a4c69e 100644 --- a/netbox/netbox/plugins/views.py +++ b/netbox/netbox/plugins/views.py @@ -12,17 +12,6 @@ from rest_framework.reverse import reverse from rest_framework.views import APIView -class InstalledPluginsAdminView(View): - """ - Admin view for listing all installed plugins - """ - def get(self, request): - plugins = [apps.get_app_config(plugin) for plugin in settings.PLUGINS] - return render(request, 'extras/admin/plugins_list.html', { - 'plugins': plugins, - }) - - @extend_schema(exclude=True) class InstalledPluginsAPIView(APIView): """ diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 984358911..7f37f01f1 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -9,7 +9,7 @@ from account.views import LoginView, LogoutView from netbox.api.views import APIRootView, StatusView from netbox.graphql.schema import schema from netbox.graphql.views import GraphQLView -from netbox.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns +from netbox.plugins.urls import plugin_patterns, plugin_api_patterns from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx from .admin import admin_site @@ -73,7 +73,6 @@ _patterns = [ # Admin path('admin/background-tasks/', include('django_rq.urls')), - path('admin/plugins/', include(plugin_admin_patterns)), path('admin/', admin_site.urls), ] diff --git a/netbox/templates/core/plugin_list.html b/netbox/templates/core/plugin_list.html new file mode 100644 index 000000000..42f0b7156 --- /dev/null +++ b/netbox/templates/core/plugin_list.html @@ -0,0 +1,36 @@ +{% 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/extras/admin/plugins_list.html b/netbox/templates/extras/admin/plugins_list.html deleted file mode 100644 index 6795c66e6..000000000 --- a/netbox/templates/extras/admin/plugins_list.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n %} - -{% block title %}{% trans "Installed Plugins" %} {{ block.super }}{% endblock %} - -{% block breadcrumbs %} - -{% endblock %} - -{% block content_title %}

{% trans "Installed Plugins" %}{{ queue.name }}

{% endblock %} - -{% block content %} -
-
-
- - - - - - - - - - - - - {% for plugin in plugins %} - - - - - - - - - {% endfor %} - -
{% trans "Name" %}
{% trans "Package Name" %}
{% trans "Author" %}
{% trans "Author Email" %}
{% trans "Description" %}
{% trans "Version" %}
- {{ plugin.verbose_name }} - - {{ plugin.name }} - - {{ plugin.author }} - - {{ plugin.author_email }} - - {{ plugin.description }} - - {{ plugin.version }} -
-
-
-
-{% endblock %}