From 80d8161d58b18fcac2c7f3a863c906541d4826eb Mon Sep 17 00:00:00 2001
From: ZouYonghe <1259085392z@gmail.com>
Date: Sun, 30 Nov 2025 10:40:46 +0800
Subject: [PATCH] feat: add update all plugins action
---
.../locales/en-US/features/extension.json | 7 ++-
.../locales/zh-CN/features/extension.json | 7 ++-
dashboard/src/views/ExtensionPage.vue | 57 +++++++++++++++++++
3 files changed, 67 insertions(+), 4 deletions(-)
diff --git a/dashboard/src/i18n/locales/en-US/features/extension.json b/dashboard/src/i18n/locales/en-US/features/extension.json
index 911523e22..b1ec35191 100644
--- a/dashboard/src/i18n/locales/en-US/features/extension.json
+++ b/dashboard/src/i18n/locales/en-US/features/extension.json
@@ -32,7 +32,8 @@
"actions": "Actions",
"back": "Back",
"selectFile": "Select File",
- "refresh": "Refresh"
+ "refresh": "Refresh",
+ "updateAll": "Update All"
},
"status": {
"enabled": "Enabled",
@@ -141,7 +142,9 @@
"confirmDelete": "Are you sure you want to delete this extension?",
"fillUrlOrFile": "Please fill in extension URL or upload extension file",
"dontFillBoth": "Please don't fill in both extension URL and upload file",
- "supportedFormats": "Supports .zip extension files"
+ "supportedFormats": "Supports .zip extension files",
+ "updateAllSuccess": "All upgradable extensions have been updated!",
+ "updateAllFailed": "{failed} of {total} extensions failed to update:"
},
"upload": {
"fromFile": "Install from File",
diff --git a/dashboard/src/i18n/locales/zh-CN/features/extension.json b/dashboard/src/i18n/locales/zh-CN/features/extension.json
index e52494d7c..cf7273042 100644
--- a/dashboard/src/i18n/locales/zh-CN/features/extension.json
+++ b/dashboard/src/i18n/locales/zh-CN/features/extension.json
@@ -32,7 +32,8 @@
"actions": "操作",
"back": "返回",
"selectFile": "选择文件",
- "refresh": "刷新"
+ "refresh": "刷新",
+ "updateAll": "更新全部"
},
"status": {
"enabled": "启用",
@@ -141,7 +142,9 @@
"confirmDelete": "确定要删除插件吗?",
"fillUrlOrFile": "请填写插件链接或上传插件文件",
"dontFillBoth": "请不要同时填写插件链接和上传文件",
- "supportedFormats": "支持 .zip 格式的插件文件"
+ "supportedFormats": "支持 .zip 格式的插件文件",
+ "updateAllSuccess": "所有可更新的插件都已更新!",
+ "updateAllFailed": "有 {failed}/{total} 个插件更新失败:"
},
"upload": {
"fromFile": "从文件安装",
diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue
index 42f981a44..a51200e60 100644
--- a/dashboard/src/views/ExtensionPage.vue
+++ b/dashboard/src/views/ExtensionPage.vue
@@ -42,6 +42,7 @@ const loadingDialog = reactive({
const showPluginInfoDialog = ref(false);
const selectedPlugin = ref({});
const curr_namespace = ref("");
+const updatingAll = ref(false);
const readmeDialog = reactive({
show: false,
@@ -226,6 +227,10 @@ const paginatedPlugins = computed(() => {
return sortedPlugins.value.slice(start, end);
});
+const updatableExtensions = computed(() => {
+ return extension_data?.data?.filter(ext => ext.has_update) || [];
+});
+
// 方法
const toggleShowReserved = () => {
showReserved.value = !showReserved.value;
@@ -372,6 +377,52 @@ const updateExtension = async (extension_name) => {
}
};
+const updateAllExtensions = async () => {
+ if (updatingAll.value || updatableExtensions.value.length === 0) return;
+ updatingAll.value = true;
+ loadingDialog.title = tm('status.loading');
+ loadingDialog.statusCode = 0;
+ loadingDialog.result = "";
+ loadingDialog.show = true;
+
+ const failures = [];
+ const targets = [...updatableExtensions.value];
+
+ for (const ext of targets) {
+ 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}`);
+ }
+ } catch (err) {
+ const errorMsg = err.response?.data?.message || err.message || String(err);
+ failures.push(`${ext.name}: ${errorMsg}`);
+ }
+ }
+
+ try {
+ await getExtensions();
+ } catch (err) {
+ const errorMsg = err.response?.data?.message || err.message || String(err);
+ failures.push(tm('messages.refreshFailed') + " " + errorMsg);
+ }
+
+ 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) => {
try {
const res = await axios.post('/api/plugin/on', { name: extension.name });
@@ -720,6 +771,12 @@ watch(marketSearch, (newVal) => {
{{ showReserved ? tm('buttons.hideSystemPlugins') : tm('buttons.showSystemPlugins') }}
+
+ mdi-update
+ {{ tm('buttons.updateAll') }}
+
+
mdi-plus
{{ tm('buttons.install') }}