14731 plugin catalog

This commit is contained in:
Arthur Hanson 2024-06-27 13:44:18 -07:00
parent c22463f4aa
commit 6953c5e759
5 changed files with 225 additions and 0 deletions

View File

@ -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/<str:name>/', views.PluginView.as_view(), name='plugin'),
)

View File

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

View File

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

View File

@ -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 %}
<li class="breadcrumb-item"><a href="{% url 'core:plugin_list' %}">{% trans "Plugins" %}</a></li>
{% endblock breadcrumbs %}
{% block subtitle %}
<div class="text-secondary fs-5">
{{ plugin.description }}
</div>
{% endblock subtitle %}
{% block controls %}
<div class="text-secondary">
From
</div>
{{ plugin.author }}
{% endblock %}
{% block tabs %}
<ul class="nav nav-tabs">
<li class="nav-item" role="presentation">
<a class="nav-link active" role="tab">
{% trans "Overview" %}
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" role="tab">
{% trans "Version history" %}
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" role="tab">
{% trans "Install" %}
</a>
</li>
</ul>
{% endblock tabs %}

View File

@ -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 %}
<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 %}
<p>{% trans "NetBox plugins enable you to document and model new kinds of resources, connect automations, add workflows, and much more." %}</p>
{# System status #}
<div class="row mb-3">
{% for plugin in plugins %}
<div class="col-md-3">
<a href="{% url 'core:plugin' plugin.slug %}">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ plugin.name }}</h5>
<p>By {{ plugin.author }}</p>
<div>
{% if plugin.is_local %}
<button type="button" class="btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-server"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" /><path d="M3 12m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" /><path d="M7 8l0 .01" /><path d="M7 16l0 .01" /></svg>
Local
</button>
{% endif %}
{% if plugin.is_certified %}
<button type="button" class="btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" class="icon icon-tabler icons-tabler-filled icon-tabler-award"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19.496 13.983l1.966 3.406a1.001 1.001 0 0 1 -.705 1.488l-.113 .011l-.112 -.001l-2.933 -.19l-1.303 2.636a1.001 1.001 0 0 1 -1.608 .26l-.082 -.094l-.072 -.11l-1.968 -3.407a8.994 8.994 0 0 0 6.93 -3.999z" /><path d="M11.43 17.982l-1.966 3.408a1.001 1.001 0 0 1 -1.622 .157l-.076 -.1l-.064 -.114l-1.304 -2.635l-2.931 .19a1.001 1.001 0 0 1 -1.022 -1.29l.04 -.107l.05 -.1l1.968 -3.409a8.994 8.994 0 0 0 6.927 4.001z" /><path d="M12 2l.24 .004a7 7 0 0 1 6.76 6.996l-.003 .193l-.007 .192l-.018 .245l-.026 .242l-.024 .178a6.985 6.985 0 0 1 -.317 1.268l-.116 .308l-.153 .348a7.001 7.001 0 0 1 -12.688 -.028l-.13 -.297l-.052 -.133l-.08 -.217l-.095 -.294a6.96 6.96 0 0 1 -.093 -.344l-.06 -.271l-.049 -.271l-.02 -.139l-.039 -.323l-.024 -.365l-.006 -.292a7 7 0 0 1 6.76 -6.996l.24 -.004z" /></svg>
Certified
</button>
{% endif %}
{% if plugin.is_community %}
<button type="button" class="btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-users-group"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" /><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M17 10h2a2 2 0 0 1 2 2v1" /><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M3 13v-1a2 2 0 0 1 2 -2h2" /></svg>
Community
</button>
{% endif %}
{% if plugin.is_installed %}
<button type="button" class="btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-check"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg>
Installed
</button>
{% endif %}
</div>
</div>
</div>
</a>
</div>
{% endfor %}
</div>
{% endblock content %}