mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 16:48:16 -06:00
14731 plugin catalog
This commit is contained in:
parent
c22463f4aa
commit
6953c5e759
@ -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'),
|
||||
)
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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'),
|
||||
|
46
netbox/templates/core/plugin.html
Normal file
46
netbox/templates/core/plugin.html
Normal 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 %}
|
65
netbox/templates/core/plugin_list.html
Normal file
65
netbox/templates/core/plugin_list.html
Normal 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 %}
|
Loading…
Reference in New Issue
Block a user