diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index e73ac6e1ca..e71912985b 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -128,10 +128,15 @@ class PluginInstallFromPkgApi(Resource): tenant_id = user.current_tenant_id parser = reqparse.RequestParser() - parser.add_argument("plugin_unique_identifier", type=str, required=True, location="json") + parser.add_argument("plugin_unique_identifiers", type=list, required=True, location="json") args = parser.parse_args() - response = PluginService.install_from_local_pkg(tenant_id, args["plugin_unique_identifier"]) + # check if all plugin_unique_identifiers are valid string + for plugin_unique_identifier in args["plugin_unique_identifiers"]: + if not isinstance(plugin_unique_identifier, str): + raise ValueError("Invalid plugin unique identifier") + + response = PluginService.install_from_local_pkg(tenant_id, args["plugin_unique_identifiers"]) return response.model_dump() @@ -155,7 +160,11 @@ class PluginInstallFromGithubApi(Resource): args = parser.parse_args() response = PluginService.install_from_github( - tenant_id, args["repo"], args["version"], args["package"], args["plugin_unique_identifier"] + tenant_id, + args["plugin_unique_identifier"], + args["repo"], + args["version"], + args["package"], ) return response.model_dump() @@ -173,10 +182,15 @@ class PluginInstallFromMarketplaceApi(Resource): tenant_id = user.current_tenant_id parser = reqparse.RequestParser() - parser.add_argument("plugin_unique_identifier", type=str, required=True, location="json") + parser.add_argument("plugin_unique_identifiers", type=list, required=True, location="json") args = parser.parse_args() - response = PluginService.install_from_marketplace_pkg(tenant_id, args["plugin_unique_identifier"]) + # check if all plugin_unique_identifiers are valid string + for plugin_unique_identifier in args["plugin_unique_identifiers"]: + if not isinstance(plugin_unique_identifier, str): + raise ValueError("Invalid plugin unique identifier") + + response = PluginService.install_from_marketplace_pkg(tenant_id, args["plugin_unique_identifiers"]) return response.model_dump() @@ -210,7 +224,14 @@ class PluginFetchInstallTasksApi(Resource): tenant_id = user.current_tenant_id - return {"tasks": PluginService.fetch_install_tasks(tenant_id)} + parser = reqparse.RequestParser() + parser.add_argument("page", type=int, required=True, location="args") + parser.add_argument("page_size", type=int, required=True, location="args") + args = parser.parse_args() + + return jsonable_encoder( + {"tasks": PluginService.fetch_install_tasks(tenant_id, args["page"], args["page_size"])} + ) class PluginFetchInstallTaskApi(Resource): @@ -224,7 +245,7 @@ class PluginFetchInstallTaskApi(Resource): tenant_id = user.current_tenant_id - return {"task": PluginService.fetch_install_task(tenant_id, task_id)} + return jsonable_encoder({"task": PluginService.fetch_install_task(tenant_id, task_id)}) class PluginUninstallApi(Resource): diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py index 03cd069d6e..c7ebcdb743 100644 --- a/api/core/plugin/entities/plugin_daemon.py +++ b/api/core/plugin/entities/plugin_daemon.py @@ -130,6 +130,6 @@ class PluginInstallTask(BasePluginEntity): plugins: list[PluginInstallTaskPluginStatus] = Field(description="The status of the plugins.") -class PluginInstallTaskStartResponse(BasePluginEntity): +class PluginInstallTaskStartResponse(BaseModel): all_installed: bool = Field(description="Whether all plugins are installed.") task_id: str = Field(description="The ID of the install task.") diff --git a/api/core/plugin/manager/plugin.py b/api/core/plugin/manager/plugin.py index 3766bf33c0..d187029357 100644 --- a/api/core/plugin/manager/plugin.py +++ b/api/core/plugin/manager/plugin.py @@ -72,7 +72,7 @@ class PluginInstallationManager(BasePluginManager): headers={"Content-Type": "application/json"}, ) - def fetch_plugin_installation_tasks(self, tenant_id: str) -> Sequence[PluginInstallTask]: + def fetch_plugin_installation_tasks(self, tenant_id: str, page: int, page_size: int) -> Sequence[PluginInstallTask]: """ Fetch plugin installation tasks. """ @@ -80,6 +80,7 @@ class PluginInstallationManager(BasePluginManager): "GET", f"plugin/{tenant_id}/management/install/tasks", list[PluginInstallTask], + params={"page": page, "page_size": page_size}, ) def fetch_plugin_installation_task(self, tenant_id: str, task_id: str) -> PluginInstallTask: diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index 2ecb8f19a1..3a0d597be1 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -51,9 +51,9 @@ class PluginService: return manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier) @staticmethod - def fetch_install_tasks(tenant_id: str) -> Sequence[PluginInstallTask]: + def fetch_install_tasks(tenant_id: str, page: int, page_size: int) -> Sequence[PluginInstallTask]: manager = PluginInstallationManager() - return manager.fetch_plugin_installation_tasks(tenant_id) + return manager.fetch_plugin_installation_tasks(tenant_id, page, page_size) @staticmethod def fetch_install_task(tenant_id: str, task_id: str) -> PluginInstallTask: @@ -61,17 +61,19 @@ class PluginService: return manager.fetch_plugin_installation_task(tenant_id, task_id) @staticmethod - def upload_pkg(tenant_id: str, pkg: bytes) -> str: + def upload_pkg(tenant_id: str, pkg: bytes, verify_signature: bool = False) -> str: """ Upload plugin package files returns: plugin_unique_identifier """ manager = PluginInstallationManager() - return manager.upload_pkg(tenant_id, pkg) + return manager.upload_pkg(tenant_id, pkg, verify_signature) @staticmethod - def upload_pkg_from_github(tenant_id: str, repo: str, version: str, package: str) -> str: + def upload_pkg_from_github( + tenant_id: str, repo: str, version: str, package: str, verify_signature: bool = False + ) -> str: """ Install plugin from github release package files, returns plugin_unique_identifier @@ -84,14 +86,15 @@ class PluginService: return manager.upload_pkg( tenant_id, pkg, + verify_signature, ) @staticmethod - def install_from_local_pkg(tenant_id: str, plugin_unique_identifier: str): + def install_from_local_pkg(tenant_id: str, plugin_unique_identifiers: Sequence[str]): manager = PluginInstallationManager() return manager.install_from_identifiers( tenant_id, - [plugin_unique_identifier], + plugin_unique_identifiers, PluginInstallationSource.Package, {}, ) @@ -115,21 +118,28 @@ class PluginService: ) @staticmethod - def install_from_marketplace_pkg(tenant_id: str, plugin_unique_identifier: str): + def install_from_marketplace_pkg( + tenant_id: str, plugin_unique_identifiers: Sequence[str], verify_signature: bool = False + ): """ Install plugin from marketplace package files, returns installation task id """ manager = PluginInstallationManager() - pkg = download_plugin_pkg(plugin_unique_identifier) - - # upload pkg to plugin daemon - pkg_id = manager.upload_pkg(tenant_id, pkg) + # check if already downloaded + for plugin_unique_identifier in plugin_unique_identifiers: + try: + manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier) + # already downloaded, skip + except Exception: + # plugin not installed, download and upload pkg + pkg = download_plugin_pkg(plugin_unique_identifier) + manager.upload_pkg(tenant_id, pkg, verify_signature) return manager.install_from_identifiers( tenant_id, - [pkg_id], + plugin_unique_identifiers, PluginInstallationSource.Marketplace, { "plugin_unique_identifier": plugin_unique_identifier,