From fceab0c82eaec0c3b9dd29990c37fd9a64d7ea34 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 13 Oct 2025 11:11:29 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1=E6=8F=90=E4=BE=9B=E5=95=86?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E6=B5=8B=E8=AF=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/components/shared/ItemCard.vue | 4 + .../i18n/locales/en-US/features/provider.json | 6 +- .../i18n/locales/zh-CN/features/provider.json | 6 +- dashboard/src/views/ProviderPage.vue | 143 ++++++++++++------ dashboard/vite.config.ts | 2 +- 5 files changed, 111 insertions(+), 50 deletions(-) diff --git a/dashboard/src/components/shared/ItemCard.vue b/dashboard/src/components/shared/ItemCard.vue index 14453ae7a..c56ae0ede 100644 --- a/dashboard/src/components/shared/ItemCard.vue +++ b/dashboard/src/components/shared/ItemCard.vue @@ -28,6 +28,7 @@ variant="outlined" color="error" rounded="xl" + :disabled="loading" @click="$emit('delete', item)" > {{ t('core.common.itemCard.delete') }} @@ -36,6 +37,7 @@ variant="tonal" color="primary" rounded="xl" + :disabled="loading" @click="$emit('edit', item)" > {{ t('core.common.itemCard.edit') }} @@ -45,10 +47,12 @@ variant="tonal" color="secondary" rounded="xl" + :disabled="loading" @click="$emit('copy', item)" > {{ t('core.common.itemCard.copy') }} + diff --git a/dashboard/src/i18n/locales/en-US/features/provider.json b/dashboard/src/i18n/locales/en-US/features/provider.json index ad32bde5d..7888c9e0b 100644 --- a/dashboard/src/i18n/locales/en-US/features/provider.json +++ b/dashboard/src/i18n/locales/en-US/features/provider.json @@ -31,7 +31,8 @@ "available": "Available", "unavailable": "Unavailable", "pending": "Pending...", - "errorMessage": "Error Message" + "errorMessage": "Error Message", + "test": "Test" }, "logs": { "title": "Service Logs", @@ -76,7 +77,8 @@ }, "error": { "sessionSeparation": "Failed to get session isolation configuration", - "fetchStatus": "Failed to get service provider status" + "fetchStatus": "Failed to get service provider status", + "testError": "Test failed for {id}: {error}" }, "confirm": { "delete": "Are you sure you want to delete service provider {id}?" diff --git a/dashboard/src/i18n/locales/zh-CN/features/provider.json b/dashboard/src/i18n/locales/zh-CN/features/provider.json index d82deed5a..c1016f061 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/provider.json +++ b/dashboard/src/i18n/locales/zh-CN/features/provider.json @@ -32,7 +32,8 @@ "available": "可用", "unavailable": "不可用", "pending": "检查中...", - "errorMessage": "错误信息" + "errorMessage": "错误信息", + "test": "测试" }, "logs": { "title": "服务日志", @@ -77,7 +78,8 @@ }, "error": { "sessionSeparation": "获取会话隔离配置失败", - "fetchStatus": "获取服务提供商状态失败" + "fetchStatus": "获取服务提供商状态失败", + "testError": "测试 {id} 失败: {error}" }, "confirm": { "delete": "确定要删除服务提供商 {id} 吗?" diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue index 4aa012cf1..c0b71fb00 100644 --- a/dashboard/src/views/ProviderPage.vue +++ b/dashboard/src/views/ProviderPage.vue @@ -60,12 +60,24 @@ :item="provider" title-field="id" enabled-field="enable" + :loading="isProviderTesting(provider.id)" @toggle-enabled="providerStatusChange" :bglogo="getProviderIcon(provider.provider)" @delete="deleteProvider" @edit="configExistingProvider" @copy="copyProvider" :show-copy-button="true"> + @@ -79,7 +91,7 @@ mdi-heart-pulse {{ tm('availability.title') }} - + mdi-refresh {{ tm('availability.refresh') }} @@ -288,7 +300,7 @@ export default { // 供应商状态相关 providerStatuses: [], - loadingStatus: false, + testingProviders: [], // 存储正在测试的 provider ID // 新增提供商对话框相关 showAddProviderDialog: false, @@ -359,7 +371,8 @@ export default { statusUpdate: this.tm('messages.success.statusUpdate'), }, error: { - fetchStatus: this.tm('messages.error.fetchStatus') + fetchStatus: this.tm('messages.error.fetchStatus'), + testError: this.tm('messages.error.testError') }, confirm: { delete: this.tm('messages.confirm.delete') @@ -368,6 +381,9 @@ export default { available: this.tm('availability.available'), unavailable: this.tm('availability.unavailable'), pending: this.tm('availability.pending') + }, + availability: { + test: this.tm('availability.test') } }; }, @@ -615,70 +631,107 @@ export default { // 获取供应商状态 async fetchProviderStatus() { - if (this.loadingStatus) return; + if (this.testingProviders.length > 0) return; - this.loadingStatus = true; this.showStatus = true; // 自动展开状态部分 - // 1. 立即初始化UI为pending状态 - this.providerStatuses = this.config_data.provider.map(p => ({ - id: p.id, - name: p.id, - status: 'pending', - error: null - })); + const providersToTest = this.config_data.provider.filter(p => p.enable); + if (providersToTest.length === 0) return; - // 2. 为每个provider创建一个并发的测试请求 - const promises = this.config_data.provider.map(p => { - if (!p.enable) { - const index = this.providerStatuses.findIndex(s => s.id === p.id); - if (index !== -1) { - const disabledStatus = { - ...this.providerStatuses[index], - status: 'unavailable', - error: '该提供商未被用户启用' - }; - this.providerStatuses.splice(index, 1, disabledStatus); - } - return Promise.resolve(); - } + // 1. 初始化UI为pending状态,并将所有待测试的 provider ID 加入 loading 列表 + this.providerStatuses = providersToTest.map(p => { + this.testingProviders.push(p.id); + return { id: p.id, name: p.id, status: 'pending', error: null }; + }); - return axios.get(`/api/config/provider/check_one?id=${p.id}`) + // 2. 为每个provider创建一个并发的测试请求 + const promises = providersToTest.map(p => + axios.get(`/api/config/provider/check_one?id=${p.id}`) .then(res => { if (res.data && res.data.status === 'ok') { - // 成功,更新对应的provider状态 const index = this.providerStatuses.findIndex(s => s.id === p.id); - if (index !== -1) { - this.providerStatuses.splice(index, 1, res.data.data); - } + if (index !== -1) this.providerStatuses.splice(index, 1, res.data.data); } else { - // 接口返回了业务错误 throw new Error(res.data?.message || `Failed to check status for ${p.id}`); } }) .catch(err => { - // 网络错误或业务错误 const errorMessage = err.response?.data?.message || err.message || 'Unknown error'; const index = this.providerStatuses.findIndex(s => s.id === p.id); if (index !== -1) { - const failedStatus = { - ...this.providerStatuses[index], - status: 'unavailable', - error: errorMessage - }; + const failedStatus = { ...this.providerStatuses[index], status: 'unavailable', error: errorMessage }; this.providerStatuses.splice(index, 1, failedStatus); } - // 可以在这里选择性地向上抛出错误,以便Promise.allSettled知道 - return Promise.reject(errorMessage); - }); - }); + return Promise.reject(errorMessage); // Propagate error for Promise.allSettled + }) + ); - // 3. 等待所有请求完成(无论成功或失败) + // 3. 等待所有请求完成 try { await Promise.allSettled(promises); } finally { - // 4. 关闭全局加载状态 - this.loadingStatus = false; + // 4. 关闭所有加载状态 + this.testingProviders = []; + } + }, + + isProviderTesting(providerId) { + return this.testingProviders.includes(providerId); + }, + + async testSingleProvider(provider) { + if (this.isProviderTesting(provider.id)) return; + + this.testingProviders.push(provider.id); + this.showStatus = true; // 自动展开状态部分 + + // 更新UI为pending状态 + const statusIndex = this.providerStatuses.findIndex(s => s.id === provider.id); + const pendingStatus = { + id: provider.id, + name: provider.id, + status: 'pending', + error: null + }; + if (statusIndex !== -1) { + this.providerStatuses.splice(statusIndex, 1, pendingStatus); + } else { + this.providerStatuses.unshift(pendingStatus); + } + + try { + if (!provider.enable) { + throw new Error('该提供商未被用户启用'); + } + + const res = await axios.get(`/api/config/provider/check_one?id=${provider.id}`); + if (res.data && res.data.status === 'ok') { + const index = this.providerStatuses.findIndex(s => s.id === provider.id); + if (index !== -1) { + this.providerStatuses.splice(index, 1, res.data.data); + } + } else { + throw new Error(res.data?.message || `Failed to check status for ${provider.id}`); + } + } catch (err) { + const errorMessage = err.response?.data?.message || err.message || 'Unknown error'; + const index = this.providerStatuses.findIndex(s => s.id === provider.id); + const failedStatus = { + id: provider.id, + name: provider.id, + status: 'unavailable', + error: errorMessage + }; + if (index !== -1) { + this.providerStatuses.splice(index, 1, failedStatus); + } + // 不再显示全局的错误提示,因为卡片本身会显示错误信息 + // this.showError(this.tm('messages.error.testError', { id: provider.id, error: errorMessage })); + } finally { + const index = this.testingProviders.indexOf(provider.id); + if (index > -1) { + this.testingProviders.splice(index, 1); + } } }, diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts index 673187ded..b52ad65b6 100644 --- a/dashboard/vite.config.ts +++ b/dashboard/vite.config.ts @@ -39,7 +39,7 @@ export default defineConfig({ port: 3000, proxy: { '/api': { - target: 'http://localhost:6185/', + target: 'http://127.0.0.1:6185/', changeOrigin: true, } } From edc83b582a83a8511776156329d502d3512d7605 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Mon, 13 Oct 2025 12:04:01 +0800 Subject: [PATCH 2/2] feat: add small size to action buttons in ItemCard and ProviderPage for better UI consistency --- dashboard/src/components/shared/ItemCard.vue | 3 +++ dashboard/src/views/ProviderPage.vue | 2 ++ 2 files changed, 5 insertions(+) diff --git a/dashboard/src/components/shared/ItemCard.vue b/dashboard/src/components/shared/ItemCard.vue index c56ae0ede..0b7c56a59 100644 --- a/dashboard/src/components/shared/ItemCard.vue +++ b/dashboard/src/components/shared/ItemCard.vue @@ -27,6 +27,7 @@