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">
+
+
+ {{ tm('availability.test') }}
+
+
@@ -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 @@