From 6953c5e759d3c7ec707584c0c4d634d05cb3118d Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Thu, 27 Jun 2024 13:44:18 -0700 Subject: [PATCH] 14731 plugin catalog --- netbox/core/urls.py | 4 + netbox/core/views.py | 105 +++++++++++++++++++++++++ netbox/netbox/navigation/menu.py | 5 ++ netbox/templates/core/plugin.html | 46 +++++++++++ netbox/templates/core/plugin_list.html | 65 +++++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 netbox/templates/core/plugin.html create mode 100644 netbox/templates/core/plugin_list.html diff --git a/netbox/core/urls.py b/netbox/core/urls.py index 58e96d735..fd6ec8996 100644 --- a/netbox/core/urls.py +++ b/netbox/core/urls.py @@ -49,4 +49,8 @@ urlpatterns = ( # System path('system/', views.SystemView.as_view(), name='system'), + + # Plugins + path('plugins/', views.PluginListView.as_view(), name='plugin_list'), + path('plugins//', views.PluginView.as_view(), name='plugin'), ) diff --git a/netbox/core/views.py b/netbox/core/views.py index a976c1ec6..e465822b0 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -1,5 +1,8 @@ +import importlib +import importlib.util import json import platform +import requests from django import __version__ as DJANGO_VERSION from django.apps import apps @@ -650,3 +653,105 @@ class SystemView(UserPassesTestMixin, View): 'plugins_table': plugins_table, 'config': config, }) + + +# +# Plugins +# + +def get_local_plugins(plugins): + for plugin_name in settings.PLUGINS: + plugin = importlib.import_module(plugin_name) + plugin_config: PluginConfig = plugin.config + + plugin_module = "{}.{}".format(plugin_config.__module__, plugin_config.__name__) # type: ignore + plugins[plugin_config.name] = { + 'slug': plugin_config.name, + 'name': plugin_config.verbose_name, + 'description': plugin_config.description, + 'author': plugin_config.author or _('Unknown Author'), + 'version': plugin_config.version, + 'icon': None, + 'is_local': True, + 'is_installed': True, + 'is_certified': False, + 'is_community': False, + } + + return plugins + + +def get_catalog_plugins(plugins): + url = 'https://api.netbox.oss.netboxlabs.com/v1/plugins' + session = requests.Session() + + def get_pages(): + payload = {'page': '1', 'per_page': '25'} + first_page = session.get(url, params=payload).json() + yield first_page + num_pages = first_page['metadata']['pagination']['last_page'] + + for page in range(2, num_pages + 1): + payload['page'] = page + next_page = session.get(url, params=payload).json() + yield next_page + + for page in get_pages(): + for data in page['data']: + + if data['config_name'] in plugins: + plugins[data['config_name']]['is_local'] = False + plugins[data['config_name']]['is_certified'] = data['release_latest']['is_certified'] + else: + plugins[data['config_name']] = { + 'slug': data['config_name'], + 'name': data['title_short'], + 'title_long': data['title_long'], + 'description': data['description_short'], + 'author': data['author']['name'] or _('Unknown Author'), + 'version': 'x', + 'icon': None, + 'is_local': False, + 'is_installed': False, + 'is_certified': data['release_latest']['is_certified'], + 'is_community': not data['release_latest']['is_certified'], + } + + return plugins + + +class PluginListView(UserPassesTestMixin, View): + + def test_func(self): + return self.request.user.is_staff + + def get(self, request): + + # Plugins + plugins = {} + plugins = get_local_plugins(plugins) + plugins = get_catalog_plugins(plugins) + plugins = [v for k, v in plugins.items()] + plugins = sorted(plugins, key=lambda d: d['name']) + + return render(request, 'core/plugin_list.html', { + 'plugins': plugins, + }) + + +class PluginView(UserPassesTestMixin, View): + + def test_func(self): + return self.request.user.is_staff + + def get(self, request, name): + + # Plugins + plugins = {} + plugins = get_local_plugins(plugins) + plugins = get_catalog_plugins(plugins) + + plugin = plugins[name] + return render(request, 'core/plugin.html', { + 'plugin': plugin, + }) diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 6db7ac14c..d7d615a87 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -428,6 +428,11 @@ ADMIN_MENU = Menu( MenuGroup( label=_('System'), items=( + MenuItem( + link='core:plugin_list', + link_text=_('Plugins'), + auth_required=True + ), MenuItem( link='core:system', link_text=_('System'), diff --git a/netbox/templates/core/plugin.html b/netbox/templates/core/plugin.html new file mode 100644 index 000000000..c7681138c --- /dev/null +++ b/netbox/templates/core/plugin.html @@ -0,0 +1,46 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load form_helpers %} +{% load i18n %} + +{% block title %}{{ plugin.name }}{% endblock %} + +{% block object_identifier %} +{% endblock object_identifier %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block subtitle %} +
+ {{ plugin.description }} +
+{% endblock subtitle %} + +{% block controls %} +
+From +
+{{ plugin.author }} +{% endblock %} + +{% block tabs %} + +{% endblock tabs %} diff --git a/netbox/templates/core/plugin_list.html b/netbox/templates/core/plugin_list.html new file mode 100644 index 000000000..bd204b567 --- /dev/null +++ b/netbox/templates/core/plugin_list.html @@ -0,0 +1,65 @@ +{% extends 'generic/_base.html' %} +{% load buttons %} +{% load helpers %} +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block title %}{% trans "Plugins" %}{% endblock %} + +{% block controls %} +{% endblock controls %} + +{% block tabs %} + +{% endblock tabs %} + +{% block content %} +

{% trans "NetBox plugins enable you to document and model new kinds of resources, connect automations, add workflows, and much more." %}

+ + {# System status #} + + +{% endblock content %}