Files
ServerKit/backend/app/models/plugin.py
T
Juan Denis 71b341ef77 Add plugin system (backend + frontend)
Introduce a plugin installation and management system.

Backend: add InstalledPlugin model, plugin_service with download/install/enable/disable/uninstall, dynamic blueprint registration and frontend manifest generation, and plugins API endpoints (list/get/install/enable/disable/uninstall). Register plugins blueprint and attempt to hot-load installed plugin blueprints at app startup.

Frontend: add PluginLoader component (Vite import.meta.glob) to render plugin widgets, extend Marketplace UI to install/manage plugins, add API client methods for plugins, and update styles for the marketplace plugin UI. This enables installing plugins from GitHub/repos/zips and managing them from the app.
2026-03-28 15:58:34 -04:00

90 lines
3.3 KiB
Python

"""Installed plugin tracking model."""
from datetime import datetime
from app import db
import json
class InstalledPlugin(db.Model):
"""Tracks plugins installed from external sources (zips/URLs)."""
__tablename__ = 'installed_plugins'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128), nullable=False, unique=True)
display_name = db.Column(db.String(256), nullable=False)
slug = db.Column(db.String(128), nullable=False, unique=True)
version = db.Column(db.String(32), nullable=False)
description = db.Column(db.Text)
author = db.Column(db.String(128))
homepage = db.Column(db.String(512))
repository = db.Column(db.String(512))
license = db.Column(db.String(64))
category = db.Column(db.String(64))
# Where it came from
source_url = db.Column(db.String(1024))
source_type = db.Column(db.String(32), default='url') # url, local, marketplace
# Paths relative to ServerKit root
backend_path = db.Column(db.String(512)) # e.g. app/plugins/serverkit-ai
frontend_path = db.Column(db.String(512)) # e.g. src/plugins/serverkit-ai
# Blueprint registration info from manifest
entry_point = db.Column(db.String(256)) # e.g. blueprint:ai_assistant_bp
url_prefix = db.Column(db.String(256)) # e.g. /api/v1/ai-assistant
# Frontend entry from manifest
frontend_entry = db.Column(db.String(256)) # e.g. components/AiAssistant.jsx
# Full manifest stored as JSON
manifest_json = db.Column(db.Text)
# Status
STATUS_ACTIVE = 'active'
STATUS_DISABLED = 'disabled'
STATUS_ERROR = 'error'
STATUS_INSTALLING = 'installing'
status = db.Column(db.String(32), default=STATUS_INSTALLING)
error_message = db.Column(db.Text)
# Has frontend component that needs rebuild
has_frontend = db.Column(db.Boolean, default=False)
# Has backend blueprint
has_backend = db.Column(db.Boolean, default=False)
installed_by = db.Column(db.Integer, db.ForeignKey('users.id'))
installed_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@property
def manifest(self):
return json.loads(self.manifest_json) if self.manifest_json else {}
@manifest.setter
def manifest(self, v):
self.manifest_json = json.dumps(v)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'display_name': self.display_name,
'slug': self.slug,
'version': self.version,
'description': self.description,
'author': self.author,
'homepage': self.homepage,
'repository': self.repository,
'license': self.license,
'category': self.category,
'source_url': self.source_url,
'source_type': self.source_type,
'entry_point': self.entry_point,
'url_prefix': self.url_prefix,
'has_frontend': self.has_frontend,
'has_backend': self.has_backend,
'status': self.status,
'error_message': self.error_message,
'installed_at': self.installed_at.isoformat() if self.installed_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}