diff --git a/web/index.html b/web/index.html index dde16aa..5e19949 100644 --- a/web/index.html +++ b/web/index.html @@ -1,10 +1,25 @@ - - + + - Vite + Vue + TS + GPT Load - 负载均衡管理系统 +
diff --git a/web/package-lock.json b/web/package-lock.json index eef1c44..eae4185 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,9 +8,12 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@heroicons/vue": "^2.2.0", + "@types/lodash-es": "^4.17.12", "axios": "^1.10.0", "echarts": "^5.6.0", "element-plus": "^2.10.2", + "lodash-es": "^4.17.21", "pinia": "^3.0.3", "vue": "^3.5.17", "vue-router": "^4.5.1" @@ -19,6 +22,7 @@ "@types/node": "^24.0.7", "@vitejs/plugin-vue": "^6.0.0", "@vue/tsconfig": "^0.7.0", + "postcss": "^8.5.6", "typescript": "~5.8.3", "vite": "^7.0.0", "vue-tsc": "^2.2.10" @@ -538,6 +542,15 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@heroicons/vue": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@heroicons/vue/-/vue-2.2.0.tgz", + "integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==", + "license": "MIT", + "peerDependencies": { + "vue": ">= 3" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -1326,6 +1339,18 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1677,6 +1702,269 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", diff --git a/web/package.json b/web/package.json index f9c05a6..aed4611 100644 --- a/web/package.json +++ b/web/package.json @@ -9,9 +9,12 @@ "preview": "vite preview" }, "dependencies": { + "@heroicons/vue": "^2.2.0", + "@types/lodash-es": "^4.17.12", "axios": "^1.10.0", "echarts": "^5.6.0", "element-plus": "^2.10.2", + "lodash-es": "^4.17.21", "pinia": "^3.0.3", "vue": "^3.5.17", "vue-router": "^4.5.1" @@ -20,6 +23,7 @@ "@types/node": "^24.0.7", "@vitejs/plugin-vue": "^6.0.0", "@vue/tsconfig": "^0.7.0", + "postcss": "^8.5.6", "typescript": "~5.8.3", "vite": "^7.0.0", "vue-tsc": "^2.2.10" diff --git a/web/src/App.vue b/web/src/App.vue index 8ca4818..21bf0cd 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,8 +1,20 @@ + + diff --git a/web/src/api/dashboard.ts b/web/src/api/dashboard.ts index 98da43f..e2ebd74 100644 --- a/web/src/api/dashboard.ts +++ b/web/src/api/dashboard.ts @@ -1,6 +1,11 @@ import request from './index'; import type { DashboardStats } from '@/types/models'; -export const getDashboardStats = (): Promise => { - return request.get('/dashboard/stats'); +export const getDashboardData = (timeRange: string, groupId: number | null): Promise => { + const params = new URLSearchParams(); + params.append('time_range', timeRange); + if (groupId) { + params.append('group_id', groupId.toString()); + } + return request.get(`/dashboard/data?${params.toString()}`); }; \ No newline at end of file diff --git a/web/src/api/groups.ts b/web/src/api/groups.ts index 1400c0e..6b2326e 100644 --- a/web/src/api/groups.ts +++ b/web/src/api/groups.ts @@ -20,7 +20,7 @@ export const fetchGroup = (id: string): Promise => { * 创建一个新的分组 * @param groupData 新分组的数据 */ -export const createGroup = (groupData: Omit): Promise => { +export const createGroup = (groupData: Omit): Promise => { return apiClient.post('/groups', groupData).then(res => res.data.data); }; @@ -29,7 +29,7 @@ export const createGroup = (groupData: Omit>): Promise => { +export const updateGroup = (id: string, groupData: Partial>): Promise => { return apiClient.put(`/groups/${id}`, groupData).then(res => res.data.data); }; diff --git a/web/src/api/keys.ts b/web/src/api/keys.ts index 6118cf7..da70eac 100644 --- a/web/src/api/keys.ts +++ b/web/src/api/keys.ts @@ -1,12 +1,12 @@ -import apiClient from './index'; -import type { Key } from '../types/models'; +import apiClient from "./index"; +import type { Key } from "../types/models"; /** * 获取指定分组下的所有密钥列表 * @param groupId 分组ID */ export const fetchKeysInGroup = (groupId: string): Promise => { - return apiClient.get(`/groups/${groupId}/keys`).then(res => res.data.data); + return apiClient.get(`/groups/${groupId}/keys`).then((res) => res.data.data); }; /** @@ -14,8 +14,21 @@ export const fetchKeysInGroup = (groupId: string): Promise => { * @param groupId 分组ID * @param keyData 新密钥的数据 */ -export const createKey = (groupId: string, keyData: Omit): Promise => { - return apiClient.post(`/groups/${groupId}/keys`, keyData).then(res => res.data.data); +export const createKey = ( + groupId: string, + keyData: Omit< + Key, + | "id" + | "group_id" + | "created_at" + | "updated_at" + | "request_count" + | "failure_count" + > +): Promise => { + return apiClient + .post(`/groups/${groupId}/keys`, keyData) + .then((res) => res.data.data); }; /** @@ -23,8 +36,8 @@ export const createKey = (groupId: string, keyData: Omit>): Promise => { - return apiClient.put(`/keys/${id}`, keyData).then(res => res.data.data); +export const updateKey = (id: string, keyData: Partial): Promise => { + return apiClient.put(`/keys/${id}`, keyData).then((res) => res.data.data); }; /** @@ -32,5 +45,27 @@ export const updateKey = (id: string, keyData: Partial => { - return apiClient.delete(`/keys/${id}`).then(res => res.data); -}; \ No newline at end of file + return apiClient.delete(`/keys/${id}`).then((res) => res.data); +}; + +/** + * 批量更新密钥 + * @param ids 密钥ID列表 + * @param data 要更新的数据 + */ +export const batchUpdateKeys = ( + ids: string[], + data: Partial +): Promise => { + return apiClient + .post("/keys/batch-update", { ids, data }) + .then((res) => res.data); +}; + +/** + * 批量删除密钥 + * @param ids 密钥ID列表 + */ +export const batchDeleteKeys = (ids: string[]): Promise => { + return apiClient.post("/keys/batch-delete", { ids }).then((res) => res.data); +}; diff --git a/web/src/api/settings.ts b/web/src/api/settings.ts index 0b3d344..4dcab4d 100644 --- a/web/src/api/settings.ts +++ b/web/src/api/settings.ts @@ -1,10 +1,22 @@ import request from './index'; -import type { Setting } from '@/types/models'; +import type { SettingCategory, SystemSettings } from '@/types/models'; -export function getSettings() { - return request.get('/settings'); +// A generic function to get settings for a specific category +export function getSettings(category: SettingCategory) { + // The backend API would need to support this, e.g., /api/settings/system + return request.get(`/settings/${category}`); } -export function updateSettings(settings: Setting[]) { - return request.put('/settings', settings); +// A generic function to update settings for a specific category +export function updateSettings(category: SettingCategory, settings: T) { + return request.put(`/settings/${category}`, settings); +} + +// Specific functions for system settings as an example +export function getSystemSettings() { + return getSettings('system'); +} + +export function updateSystemSettings(settings: SystemSettings) { + return updateSettings('system', settings); } \ No newline at end of file diff --git a/web/src/components/GroupConfigForm.vue b/web/src/components/GroupConfigForm.vue index 5c2708e..1a8b85a 100644 --- a/web/src/components/GroupConfigForm.vue +++ b/web/src/components/GroupConfigForm.vue @@ -61,7 +61,7 @@ watch( if (newGroup) { formData.name = newGroup.name; formData.description = newGroup.description; - formData.is_default = newGroup.is_default; + formData.is_default = newGroup.is_default || false; } }, { immediate: true, deep: true } @@ -73,7 +73,7 @@ const handleSave = async () => { try { await formRef.value.validate(); isSaving.value = true; - await updateGroup(groupStore.selectedGroupId, { + await updateGroup(groupStore.selectedGroupId.toString(), { name: formData.name, description: formData.description, is_default: formData.is_default, diff --git a/web/src/components/GroupList.vue b/web/src/components/GroupList.vue index d5698af..1a7c75a 100644 --- a/web/src/components/GroupList.vue +++ b/web/src/components/GroupList.vue @@ -1,13 +1,13 @@