14729 merge feature

This commit is contained in:
Arthur 2024-01-19 09:05:28 -08:00
commit d70ad54a7c
11 changed files with 130 additions and 78 deletions

View File

@ -2,3 +2,4 @@ from .config import *
from .data import * from .data import *
from .jobs import * from .jobs import *
from .tasks import * from .tasks import *
from .plugins import *

View File

@ -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',
)

View File

@ -40,4 +40,6 @@ urlpatterns = (
# Configuration # Configuration
path('config/', views.ConfigView.as_view(), name='config'), path('config/', views.ConfigView.as_view(), name='config'),
# Plugins
path('plugins/', views.PluginListView.as_view(), name='plugin_list'),
) )

View File

@ -1,5 +1,7 @@
from django.apps import apps
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpResponseForbidden, Http404 from django.http import HttpResponseForbidden, Http404
from django_rq.queues import get_queue_by_index from django_rq.queues import get_queue_by_index
@ -243,7 +245,10 @@ class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View):
# #
class BackgroundQueuesListView(LoginRequiredMixin, View): class BackgroundQueuesListView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.is_staff
def get(self, request): def get(self, request):
table = tables.BackgroundQueueTable(get_statistics(run_maintenance_tasks=True)["queues"]) table = tables.BackgroundQueueTable(get_statistics(run_maintenance_tasks=True)["queues"])
@ -253,7 +258,10 @@ class BackgroundQueuesListView(LoginRequiredMixin, View):
}) })
class BackgroundTasksListView(LoginRequiredMixin, View): class BackgroundTasksListView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.is_staff
def get(self, request, queue_index): def get(self, request, queue_index):
queue_index = int(queue_index) queue_index = int(queue_index)
@ -272,7 +280,10 @@ class BackgroundTasksListView(LoginRequiredMixin, View):
}) })
class BackgroundTaskDetailView(LoginRequiredMixin, View): class BackgroundTaskDetailView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.is_staff
def get(self, request, queue_index, job_id): def get(self, request, queue_index, job_id):
queue_index = int(queue_index) queue_index = int(queue_index)
@ -295,3 +306,27 @@ class BackgroundTaskDetailView(LoginRequiredMixin, View):
'queue_index': queue_index, 'queue_index': queue_index,
'data_is_valid': data_is_valid, 'data_is_valid': data_is_valid,
}) })
#
# 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,
})

View File

@ -1,3 +1,5 @@
from django.apps import apps
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType

View File

@ -455,6 +455,16 @@ ADMIN_MENU = Menu(
), ),
), ),
), ),
MenuGroup(
label=_('Plugins'),
items=(
MenuItem(
link='core:plugin_list',
link_text=_('Plugins'),
staff_only=True
),
),
),
), ),
) )

View File

@ -15,9 +15,6 @@ plugin_api_patterns = [
path('', views.PluginsAPIRootView.as_view(), name='api-root'), path('', views.PluginsAPIRootView.as_view(), name='api-root'),
path('installed-plugins/', views.InstalledPluginsAPIView.as_view(), name='plugins-list') 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 # Register base/API URL patterns for each plugin
for plugin_path in settings.PLUGINS: for plugin_path in settings.PLUGINS:

View File

@ -12,17 +12,6 @@ from rest_framework.reverse import reverse
from rest_framework.views import APIView 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) @extend_schema(exclude=True)
class InstalledPluginsAPIView(APIView): class InstalledPluginsAPIView(APIView):
""" """

View File

@ -9,7 +9,7 @@ from account.views import LoginView, LogoutView
from netbox.api.views import APIRootView, StatusView from netbox.api.views import APIRootView, StatusView
from netbox.graphql.schema import schema from netbox.graphql.schema import schema
from netbox.graphql.views import GraphQLView 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 netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
from .admin import admin_site from .admin import admin_site
@ -73,7 +73,6 @@ _patterns = [
# Admin # Admin
path('admin/background-tasks/', include('django_rq.urls')), path('admin/background-tasks/', include('django_rq.urls')),
path('admin/plugins/', include(plugin_admin_patterns)),
path('admin/', admin_site.urls), path('admin/', admin_site.urls),
] ]

View File

@ -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 %}
<ul class="nav nav-tabs px-3">
<li class="nav-item" role="presentation">
<a class="nav-link active" role="tab">{% trans "Plugins" %}</a>
</li>
</ul>
{% endblock tabs %}
{% block content %}
<div class="row mb-3">
<div class="col-auto ms-auto d-print-none">
{# Table configuration button #}
<div class="table-configure input-group">
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}" data-bs-target="#ObjectTable_config" class="btn">
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
</button>
</div>
</div>
</div>
<div class="card">
{% render_table table %}
</div>
{% endblock content %}
{% block modals %}
{% table_config_form table table_name="ObjectTable" %}
{% endblock modals %}

View File

@ -1,58 +0,0 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block title %}{% trans "Installed Plugins" %} {{ block.super }}{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans "Home" %}</a> &rsaquo;
<a href="{% url 'plugins_list' %}">{% trans "Installed Plugins" %}</a>
</div>
{% endblock %}
{% block content_title %}<h1>{% trans "Installed Plugins" %}{{ queue.name }}</h1>{% endblock %}
{% block content %}
<div id="content-main">
<div class="module" id="changelist">
<div class="results">
<table id="result_list">
<thead>
<tr>
<th><div class="text"><span>{% trans "Name" %}</span></div></th>
<th><div class="text"><span>{% trans "Package Name" %}</span></div></th>
<th><div class="text"><span>{% trans "Author" %}</span></div></th>
<th><div class="text"><span>{% trans "Author Email" %}</span></div></th>
<th><div class="text"><span>{% trans "Description" %}</span></div></th>
<th><div class="text"><span>{% trans "Version" %}</span></div></th>
</tr>
</thead>
<tbody>
{% for plugin in plugins %}
<tr class="{% cycle 'row1' 'row2' %}">
<td>
{{ plugin.verbose_name }}
</td>
<td>
{{ plugin.name }}
</td>
<td>
{{ plugin.author }}
</td>
<td>
{{ plugin.author_email }}
</td>
<td>
{{ plugin.description }}
</td>
<td>
{{ plugin.version }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}