mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
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 <jstretch@netboxlabs.com>
This commit is contained in:
parent
073c2dc8ca
commit
ef5e10d360
@ -1,3 +1,4 @@
|
|||||||
from .config import *
|
from .config import *
|
||||||
from .data import *
|
from .data import *
|
||||||
from .jobs import *
|
from .jobs import *
|
||||||
|
from .plugins import *
|
||||||
|
39
netbox/core/tables/plugins.py
Normal file
39
netbox/core/tables/plugins.py
Normal 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',
|
||||||
|
)
|
@ -35,4 +35,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'),
|
||||||
)
|
)
|
||||||
|
@ -1,4 +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 UserPassesTestMixin
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
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}")
|
messages.success(request, f"Restored configuration revision #{pk}")
|
||||||
|
|
||||||
return redirect(candidate_config.get_absolute_url())
|
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,
|
||||||
|
})
|
||||||
|
@ -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
|
||||||
|
@ -450,6 +450,16 @@ ADMIN_MENU = Menu(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
MenuGroup(
|
||||||
|
label=_('Plugins'),
|
||||||
|
items=(
|
||||||
|
MenuItem(
|
||||||
|
link='core:plugin_list',
|
||||||
|
link_text=_('Plugins'),
|
||||||
|
staff_only=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
36
netbox/templates/core/plugin_list.html
Normal file
36
netbox/templates/core/plugin_list.html
Normal 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 %}
|
@ -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> ›
|
|
||||||
<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 %}
|
|
Loading…
Reference in New Issue
Block a user