Plugin loading improvements (#6056)

* Add API endpoint to reload plugin registry

* Tweak debug

* Add elements for CUI

* Update API version

* Reload plugins from PUI
This commit is contained in:
Oliver 2023-12-11 07:46:41 +11:00 committed by GitHub
parent 256f44b44e
commit 8d46521cab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 129 additions and 7 deletions

View File

@ -2,11 +2,14 @@
# InvenTree API version
INVENTREE_API_VERSION = 158
INVENTREE_API_VERSION = 159
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v159 -> 2023-12-08 : https://github.com/inventree/InvenTree/pull/6056
- Adds API endpoint for reloading plugin registry
v158 -> 2023-11-21 : https://github.com/inventree/InvenTree/pull/5953
- Adds API endpoint for listing all settings of a particular plugin
- Adds API endpoint for registry status (errors)

View File

@ -189,6 +189,18 @@ class PluginActivate(UpdateAPI):
serializer.save()
class PluginReload(CreateAPI):
"""Endpoint for reloading all plugins."""
queryset = PluginConfig.objects.none()
serializer_class = PluginSerializers.PluginReloadSerializer
permission_classes = [IsSuperuser,]
def perform_create(self, serializer):
"""Saving the serializer instance performs plugin installation"""
return serializer.save()
class PluginSettingList(ListAPI):
"""List endpoint for all plugin related settings.
@ -374,6 +386,7 @@ plugin_api_urls = [
re_path('^metadata/', MetadataView.as_view(), {'model': PluginConfig}, name='api-plugin-metadata'),
# Plugin management
re_path(r'^reload/', PluginReload.as_view(), name='api-plugin-reload'),
re_path(r'^install/', PluginInstall.as_view(), name='api-plugin-install'),
re_path(r'^activate/', PluginActivate.as_view(), name='api-plugin-activate'),

View File

@ -260,7 +260,7 @@ class PluginsRegistry:
if self.loading_lock.acquire(blocking=False):
logger.info('Plugin Registry: Reloading plugins')
logger.info('Plugin Registry: Reloading plugins - Force: %s, Full: %s, Collect: %s', force_reload, full_reload, collect)
with maintenance_mode_on():
if collect:

View File

@ -131,6 +131,37 @@ class PluginConfigEmptySerializer(serializers.Serializer):
...
class PluginReloadSerializer(serializers.Serializer):
"""Serializer for remotely forcing plugin registry reload"""
full_reload = serializers.BooleanField(
required=False, default=False,
label=_("Full reload"),
help_text=_("Perform a full reload of the plugin registry")
)
force_reload = serializers.BooleanField(
required=False, default=False,
label=_("Force reload"),
help_text=_("Force a reload of the plugin registry, even if it is already loaded")
)
collect_plugins = serializers.BooleanField(
required=False, default=False,
label=_("Collect plugins"),
help_text=_("Collect plugins and add them to the registry")
)
def save(self):
"""Reload the plugin registry."""
from plugin.registry import registry
registry.reload_plugins(
full_reload=self.validated_data.get('full_reload', False),
force_reload=self.validated_data.get('force_reload', False),
collect=self.validated_data.get('collect_plugins', False),
)
class PluginActivateSerializer(serializers.Serializer):
"""Serializer for activating or deactivating a plugin"""

View File

@ -38,7 +38,13 @@
{% admin_url user "plugin.pluginconfig" None as url %}
{% include "admin_button.html" with url=url %}
{% if plug %}
<button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}"><span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %}</button>
<button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}">
<span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %}
</button>
<button class='btn btn-success' id='reload-plugins' title='{% trans "Reload Plugins" %}'>
<span class='fas fa-redo-alt'></span> {% trans "Reload Plugins" %}
</button>
</button>
{% endif %}
</div>
</div>

View File

@ -591,5 +591,10 @@ onPanelLoad('plugin', function() {
installPlugin();
});
// Callback to reload plugins
$('#reload-plugins').click(function() {
reloadPlugins();
});
{% endif %}
});

View File

@ -20,7 +20,8 @@
activatePlugin,
installPlugin,
loadPluginTable,
locateItemOrLocation
locateItemOrLocation,
reloadPlugins,
*/
@ -213,6 +214,37 @@ function activatePlugin(plugin_id, active=true) {
}
/*
* Reload the plugin registry
*/
function reloadPlugins() {
let url = '{% url "api-plugin-reload" %}';
constructForm(url, {
title: '{% trans "Reload Plugins" %}',
method: 'POST',
confirm: true,
fields: {
force_reload: {
// hidden: true,
value: true,
},
full_reload: {
// hidden: true,
value: true,
},
collect_plugins: {
// hidden: true,
value: true,
},
},
onSuccess: function() {
location.reload();
}
});
}
function locateItemOrLocation(options={}) {
if (!options.item && !options.location) {

View File

@ -11,7 +11,7 @@ import {
Tooltip
} from '@mantine/core';
import { modals } from '@mantine/modals';
import { notifications } from '@mantine/notifications';
import { notifications, showNotification } from '@mantine/notifications';
import {
IconCircleCheck,
IconCircleX,
@ -30,6 +30,7 @@ import { useCreateApiFormModal } from '../../../hooks/UseForm';
import { useInstance } from '../../../hooks/UseInstance';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl, useServerApiState } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { ActionButton } from '../../buttons/ActionButton';
import { ActionDropdown, EditItemAction } from '../../items/ActionDropdown';
import { InfoItem } from '../../items/InfoItem';
@ -423,11 +424,39 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
}
});
const user = useUserState();
const reloadPlugins = useCallback(() => {
api
.post(apiUrl(ApiPaths.plugin_reload), {
full_reload: true,
force_reload: true,
collect_plugins: true
})
.then(() => {
showNotification({
title: t`Plugins reloaded`,
message: t`Plugins were reloaded successfully`,
color: 'green'
});
table.refreshTable();
});
}, []);
// Custom table actions
const tableActions = useMemo(() => {
let actions = [];
if (pluginsEnabled) {
if (user.user?.is_superuser && pluginsEnabled) {
actions.push(
<ActionButton
color="green"
icon={<IconRefresh />}
tooltip={t`Reload Plugins`}
onClick={reloadPlugins}
/>
);
actions.push(
<ActionButton
color="green"
@ -439,7 +468,7 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
}
return actions;
}, []);
}, [user, pluginsEnabled]);
return (
<>

View File

@ -86,6 +86,7 @@ export enum ApiPaths {
plugin_list = 'api-plugin-list',
plugin_setting_list = 'api-plugin-settings',
plugin_install = 'api-plugin-install',
plugin_reload = 'api-plugin-reload',
plugin_registry_status = 'api-plugin-registry-status',
project_code_list = 'api-project-code-list',

View File

@ -187,6 +187,8 @@ export function apiEndpoint(path: ApiPaths): string {
return 'plugins/status/';
case ApiPaths.plugin_install:
return 'plugins/install/';
case ApiPaths.plugin_reload:
return 'plugins/reload/';
case ApiPaths.project_code_list:
return 'project-code/';
case ApiPaths.custom_unit_list: