diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 597a245d4..e479b8b4f 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -1,3 +1,4 @@ +import asyncio import json import os import ssl @@ -33,6 +34,7 @@ class PluginRoute(Route): "/plugin/install": ("POST", self.install_plugin), "/plugin/install-upload": ("POST", self.install_plugin_upload), "/plugin/update": ("POST", self.update_plugin), + "/plugin/update-all": ("POST", self.update_all_plugins), "/plugin/uninstall": ("POST", self.uninstall_plugin), "/plugin/market_list": ("GET", self.get_online_plugins), "/plugin/off": ("POST", self.off_plugin), @@ -432,6 +434,47 @@ class PluginRoute(Route): logger.error(f"/api/plugin/update: {traceback.format_exc()}") return Response().error(str(e)).__dict__ + async def update_all_plugins(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + + post_data = await request.json + plugin_names: list[str] = post_data.get("names") or [] + proxy: str = post_data.get("proxy", "") + + if not isinstance(plugin_names, list) or not plugin_names: + return Response().error("插件列表不能为空").__dict__ + + results = [] + sem = asyncio.Semaphore(3) # 控制并发数量 + + async def _update_one(name: str): + async with sem: + try: + logger.info(f"批量更新插件 {name}") + await self.plugin_manager.update_plugin(name, proxy) + return {"name": name, "status": "ok", "message": "更新成功"} + except Exception as e: + logger.error( + f"/api/plugin/update-all: 更新插件 {name} 失败: {traceback.format_exc()}", + ) + return {"name": name, "status": "error", "message": str(e)} + + results = await asyncio.gather(*[_update_one(name) for name in plugin_names]) + + failed = [r for r in results if r["status"] == "error"] + message = ( + "批量更新完成,全部成功。" + if not failed + else f"批量更新完成,其中 {len(failed)}/{len(results)} 个插件失败。" + ) + + return Response().ok({"results": results}, message).__dict__ + async def off_plugin(self): if DEMO_MODE: return ( diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue index a51200e60..a9dec0679 100644 --- a/dashboard/src/views/ExtensionPage.vue +++ b/dashboard/src/views/ExtensionPage.vue @@ -385,42 +385,46 @@ const updateAllExtensions = async () => { loadingDialog.result = ""; loadingDialog.show = true; - const failures = []; - const targets = [...updatableExtensions.value]; + const targets = updatableExtensions.value.map(ext => ext.name); + try { + const res = await axios.post('/api/plugin/update-all', { + names: targets, + proxy: localStorage.getItem('selectedGitHubProxy') || "" + }); - for (const ext of targets) { + if (res.data.status === "error") { + onLoadingDialogResult(2, res.data.message || tm('messages.updateAllFailed', { + failed: targets.length, + total: targets.length + }), -1); + return; + } + + const results = res.data.data?.results || []; + const failures = results.filter(r => r.status !== 'ok'); try { - const res = await axios.post('/api/plugin/update', { - name: ext.name, - proxy: localStorage.getItem('selectedGitHubProxy') || "" - }); - if (res.data.status === "error") { - failures.push(`${ext.name}: ${res.data.message}`); - } + await getExtensions(); } catch (err) { const errorMsg = err.response?.data?.message || err.message || String(err); - failures.push(`${ext.name}: ${errorMsg}`); + failures.push({ name: 'refresh', status: 'error', message: errorMsg }); } - } - try { - await getExtensions(); + if (failures.length === 0) { + onLoadingDialogResult(1, tm('messages.updateAllSuccess')); + } else { + const failureText = tm('messages.updateAllFailed', { + failed: failures.length, + total: targets.length + }); + const detail = failures.map(f => `${f.name}: ${f.message}`).join('\n'); + onLoadingDialogResult(2, `${failureText}\n${detail}`, -1); + } } catch (err) { const errorMsg = err.response?.data?.message || err.message || String(err); - failures.push(tm('messages.refreshFailed') + " " + errorMsg); + onLoadingDialogResult(2, errorMsg, -1); + } finally { + updatingAll.value = false; } - - if (failures.length === 0) { - onLoadingDialogResult(1, tm('messages.updateAllSuccess')); - } else { - const failureText = tm('messages.updateAllFailed', { - failed: failures.length, - total: targets.length - }); - onLoadingDialogResult(2, `${failureText}\n${failures.join('\n')}`, -1); - } - - updatingAll.value = false; }; const pluginOn = async (extension) => {