Add web UI to enable/disable plugins
GET /api/plugins now lists all discovered plugins (enabled or not) with status; PUT /api/plugins/<name> toggles enabled (persists to plugins.json). New Settings > Plugins admin page with per-plugin toggles. Route changes apply on next restart. Replaces CLI-only enable/disable for self-serve admin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
"""Plugin introspection API - what plugins are loaded and their contracts."""
|
||||
"""Plugin introspection + enable/disable API."""
|
||||
|
||||
from flask import Blueprint, current_app
|
||||
from flask import Blueprint, current_app, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
from shopdb.utils.responses import success_response
|
||||
from shopdb.utils.responses import success_response, error_response, ErrorCodes
|
||||
|
||||
plugins_bp = Blueprint('plugins', __name__)
|
||||
|
||||
@@ -11,27 +11,46 @@ plugins_bp = Blueprint('plugins', __name__)
|
||||
@plugins_bp.route('', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_plugins():
|
||||
"""List loaded plugins with their manifest metadata + the framework
|
||||
contract version they were checked against."""
|
||||
"""List all discovered plugins (enabled or not) with their metadata and
|
||||
the framework contract version."""
|
||||
from shopdb import __contract_version__
|
||||
pm = current_app.extensions.get('plugin_manager')
|
||||
plugins = []
|
||||
if pm:
|
||||
for name, plugin in pm.get_all_plugins().items():
|
||||
meta = plugin.meta
|
||||
plugins.append({
|
||||
'name': meta.name,
|
||||
'version': meta.version,
|
||||
'description': meta.description,
|
||||
'author': meta.author,
|
||||
'core_version': meta.core_version,
|
||||
'api_prefix': meta.api_prefix,
|
||||
'dependencies': meta.dependencies,
|
||||
})
|
||||
plugins = pm.discover_available() if pm else []
|
||||
plugins.sort(key=lambda p: p['name'])
|
||||
|
||||
return success_response({
|
||||
'contract_version': __contract_version__,
|
||||
'count': len(plugins),
|
||||
'plugins': plugins,
|
||||
})
|
||||
|
||||
|
||||
@plugins_bp.route('/<name>', methods=['PUT'])
|
||||
@jwt_required()
|
||||
def set_plugin_enabled(name: str):
|
||||
"""Enable or disable a plugin. Takes effect on the next app restart for
|
||||
route/navigation changes."""
|
||||
data = request.get_json() or {}
|
||||
if 'enabled' not in data:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'enabled is required')
|
||||
|
||||
pm = current_app.extensions.get('plugin_manager')
|
||||
if not pm:
|
||||
return error_response(ErrorCodes.INTERNAL_ERROR,
|
||||
'Plugin manager unavailable', http_code=500)
|
||||
|
||||
want = bool(data['enabled'])
|
||||
ok = pm.enable_plugin(name) if want else pm.disable_plugin(name)
|
||||
if not ok:
|
||||
# enable/disable refused (unknown plugin, or a dependency conflict)
|
||||
return error_response(
|
||||
ErrorCodes.CONFLICT,
|
||||
f"Could not {'enable' if want else 'disable'} '{name}' "
|
||||
f"(check it exists and dependencies allow it)",
|
||||
http_code=409
|
||||
)
|
||||
|
||||
return success_response(
|
||||
{'name': name, 'enabled': want},
|
||||
message=f"Plugin {'enabled' if want else 'disabled'} "
|
||||
f"(restart to apply route changes)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user