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') }}