From 75b2d130c23813a5a040b36484d33c9b3422c764 Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 15 Feb 2026 10:34:07 +0800 Subject: [PATCH] refactor: major project restructuring and cleanup Changes: - Remove deprecated Fengling.Activity and YarpGateway.Admin projects - Add points processing services with distributed lock support - Update Vben frontend with gateway management pages - Add gateway config controller and database listener - Update routing to use header-mixed-nav layout - Add comprehensive test suites for Member services - Add YarpGateway integration tests - Update package versions in Directory.Packages.props Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- apps/web-ele/.env.development | 1 + apps/web-ele/package.json | 1 + apps/web-ele/src/api/fengling/gateway.ts | 335 +++++++----------- apps/web-ele/src/router/routes/core.ts | 2 +- .../src/router/routes/modules/dashboard.ts | 38 -- .../src/router/routes/modules/demos.ts | 36 -- .../src/router/routes/modules/fengling.ts | 131 +++++-- .../web-ele/src/router/routes/modules/vben.ts | 86 +---- .../dashboard/analytics/analytics-trends.vue | 98 ----- .../analytics/analytics-visits-data.vue | 82 ----- .../analytics/analytics-visits-sales.vue | 46 --- .../analytics/analytics-visits-source.vue | 65 ---- .../dashboard/analytics/analytics-visits.vue | 55 --- .../src/views/dashboard/analytics/index.vue | 90 ----- .../src/views/dashboard/workspace/index.vue | 266 -------------- .../web-ele/src/views/demos/element/index.vue | 117 ------ apps/web-ele/src/views/demos/form/basic.vue | 191 ---------- .../src/views/fengling/gateway/clusters.vue | 294 ++++++++++----- .../src/views/fengling/gateway/index.vue | 79 ++--- .../src/views/fengling/gateway/routes.vue | 159 +++------ apps/web-ele/vite.config.mts | 5 +- packages/@core/preferences/src/config.ts | 6 +- packages/@core/preferences/src/preferences.ts | 5 +- pnpm-lock.yaml | 3 + 24 files changed, 532 insertions(+), 1659 deletions(-) delete mode 100644 apps/web-ele/src/router/routes/modules/dashboard.ts delete mode 100644 apps/web-ele/src/router/routes/modules/demos.ts delete mode 100644 apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue delete mode 100644 apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue delete mode 100644 apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue delete mode 100644 apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue delete mode 100644 apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue delete mode 100644 apps/web-ele/src/views/dashboard/analytics/index.vue delete mode 100644 apps/web-ele/src/views/dashboard/workspace/index.vue delete mode 100644 apps/web-ele/src/views/demos/element/index.vue delete mode 100644 apps/web-ele/src/views/demos/form/basic.vue diff --git a/apps/web-ele/.env.development b/apps/web-ele/.env.development index ff15679..41490b7 100644 --- a/apps/web-ele/.env.development +++ b/apps/web-ele/.env.development @@ -1,5 +1,6 @@ # 端口号 VITE_PORT=5777 +VITE_APP_TITLE=Fengling Console VITE_BASE=/ diff --git a/apps/web-ele/package.json b/apps/web-ele/package.json index da33b8c..519df98 100644 --- a/apps/web-ele/package.json +++ b/apps/web-ele/package.json @@ -26,6 +26,7 @@ "#/*": "./src/*" }, "dependencies": { + "@element-plus/icons-vue": "^2.3.2", "@vben/access": "workspace:*", "@vben/common-ui": "workspace:*", "@vben/constants": "workspace:*", diff --git a/apps/web-ele/src/api/fengling/gateway.ts b/apps/web-ele/src/api/fengling/gateway.ts index d1c627d..bf8c9a4 100644 --- a/apps/web-ele/src/api/fengling/gateway.ts +++ b/apps/web-ele/src/api/fengling/gateway.ts @@ -3,254 +3,177 @@ import type { Api } from '#/api/typings'; import { requestClient } from '#/api/request'; export namespace GatewayApi { - const apiPrefix = '/api/gateway'; + // 注意: baseURL 已包含 /api,所以这里不再重复 + const apiPrefix = '/console/gateway'; - // ========== 租户管理 ========== - export async function getTenantList(params?: { - page?: number; - pageSize?: number; - keyword?: string; + // ========== 统计数据 ========== + export async function getStatistics() { + return requestClient.get(`${apiPrefix}/statistics`); + } + + export async function getOverviewStats() { + return requestClient.get(`${apiPrefix}/statistics`); + } + + // ========== 服务管理 ========== + export async function getServices(params?: { + globalOnly?: boolean; + tenantCode?: string; }) { - return requestClient.get>(`${apiPrefix}/tenants`, { + return requestClient.get(`${apiPrefix}/services`, { params, }); } - export async function getTenantById(id: number) { - return requestClient.get(`${apiPrefix}/tenants/${id}`); + export async function getService(serviceName: string, tenantCode?: string) { + return requestClient.get( + `${apiPrefix}/services/${serviceName}`, + { params: { tenantCode } } + ); } - export async function createTenant(data: GatewayApi.CreateTenantRequest) { - return requestClient.post( - `${apiPrefix}/tenants`, + export async function createService(data: GatewayApi.CreateServiceRequest) { + return requestClient.post( + `${apiPrefix}/services`, data ); } - export async function updateTenant(id: number, data: GatewayApi.UpdateTenantRequest) { - return requestClient.put( - `${apiPrefix}/tenants/${id}`, - data + export async function deleteService(serviceName: string, tenantCode?: string) { + return requestClient.delete( + `${apiPrefix}/services/${serviceName}`, + { params: { tenantCode } } ); } - export async function deleteTenant(id: number) { - return requestClient.delete(`${apiPrefix}/tenants/${id}`); - } - // ========== 路由管理 ========== - export async function getRouteList(params?: { - page?: number; - pageSize?: number; - tenantCode?: string; - isGlobal?: boolean; + export async function getRoutes(params?: { + globalOnly?: boolean; }) { - return requestClient.get>(`${apiPrefix}/routes`, { + return requestClient.get(`${apiPrefix}/routes`, { params, }); } - export async function getGlobalRoutes() { - return requestClient.get(`${apiPrefix}/routes/global`); - } - - export async function getTenantRoutes(tenantCode: string) { - return requestClient.get(`${apiPrefix}/routes/tenant/${tenantCode}`); - } - - export async function getRouteById(id: number) { - return requestClient.get(`${apiPrefix}/routes/${id}`); + // 兼容旧的函数名 + export async function getRouteList(params?: { + globalOnly?: boolean; + }) { + return requestClient.get>(`${apiPrefix}/routes`, { + params, + }); } export async function createRoute(data: GatewayApi.CreateRouteRequest) { - return requestClient.post( + return requestClient.post( `${apiPrefix}/routes`, data ); } - export async function batchCreateRoutes(data: GatewayApi.BatchCreateRoutesRequest) { - return requestClient.post<{ successCount: number }, GatewayApi.BatchCreateRoutesRequest>( - `${apiPrefix}/routes/batch`, - data - ); - } - - export async function updateRoute(id: number, data: GatewayApi.CreateRouteRequest) { - return requestClient.put( - `${apiPrefix}/routes/${id}`, - data - ); - } - - export async function deleteRoute(id: number) { - return requestClient.delete(`${apiPrefix}/routes/${id}`); - } - - // ========== 集群管理 ========== - export async function getClusterList() { - return requestClient.get(`${apiPrefix}/clusters`); - } - - export async function getClusterById(clusterId: string) { - return requestClient.get(`${apiPrefix}/clusters/${clusterId}`); - } - - export async function createCluster(data: GatewayApi.CreateClusterRequest) { - return requestClient.post( - `${apiPrefix}/clusters`, - data - ); - } - - export async function updateCluster( - clusterId: string, - data: GatewayApi.CreateClusterRequest - ) { - return requestClient.put( - `${apiPrefix}/clusters/${clusterId}`, - data - ); - } - - export async function deleteCluster(clusterId: string) { - return requestClient.delete(`${apiPrefix}/clusters/${clusterId}`); - } - // ========== 实例管理 ========== - export async function getInstanceList(clusterId: string) { - return requestClient.get( + export async function getInstances(clusterId: string) { + return requestClient.get( `${apiPrefix}/clusters/${clusterId}/instances` ); } - export async function getInstanceById(id: number) { - return requestClient.get(`${apiPrefix}/instances/${id}`); + // 兼容旧的函数名 + export async function getInstanceList(clusterId: string) { + return requestClient.get( + `${apiPrefix}/clusters/${clusterId}/instances` + ); } - export async function createInstance(clusterId: string, data: GatewayApi.CreateInstanceRequest) { - return requestClient.post( - `${apiPrefix}/clusters/${clusterId}/instances`, + export async function createInstance(data: GatewayApi.CreateInstanceRequest) { + return requestClient.post( + `${apiPrefix}/instances`, data ); } - export async function updateInstance(id: number, data: GatewayApi.CreateInstanceRequest) { - return requestClient.put( - `${apiPrefix}/instances/${id}`, - data + export async function deleteInstance(instanceId: number) { + return requestClient.delete(`${apiPrefix}/instances/${instanceId}`); + } + + export async function updateInstanceWeight(instanceId: number, weight: number) { + return requestClient.put( + `${apiPrefix}/instances/${instanceId}/weight`, + { weight } ); } - export async function deleteInstance(id: number) { - return requestClient.delete(`${apiPrefix}/instances/${id}`); - } - - // ========== 配置热更新 ========== + // ========== 配置重载 ========== export async function reloadConfig() { - return requestClient.post<{ message: string }>(`${apiPrefix}/config/reload`); - } - - export async function getConfigStatus() { - return requestClient.get(`${apiPrefix}/config/status`); - } - - export async function getVersionInfo() { - return requestClient.get(`${apiPrefix}/config/versions`); - } - - // ========== 统计 ========== - export async function getOverviewStats() { - return requestClient.get(`${apiPrefix}/stats/overview`); + return requestClient.post<{ message: string }>(`${apiPrefix}/reload`); } // ========== 类型定义 ========== - export interface Tenant { + // 与后端 GatewayStatisticsDto 对应 + export interface GatewayStatistics { + totalServices: number; + globalRoutes: number; + tenantRoutes: number; + totalInstances: number; + healthyInstances: number; + recentServices: GatewayService[]; + } + + // 与后端 GatewayServiceDto 对应 + export interface GatewayService { id: number; - tenantCode: string; - tenantName: string; - contactName?: string; - contactEmail?: string; - contactPhone?: string; - maxUsers?: number; + servicePrefix: string; + serviceName: string; + version: string; + clusterId: string; + pathPattern: string; + serviceAddress: string; + destinationId: string; + weight: number; + instanceCount: number; + isGlobal: boolean; + tenantCode?: string; status: number; - description?: string; - routeCount: number; - version: number; - createdTime: string; - updatedTime?: string; + createdAt: string; } - export interface CreateTenantRequest { - tenantCode: string; - tenantName: string; - contactName?: string; - contactEmail?: string; - contactPhone?: string; - maxUsers?: number; - description?: string; + // 与后端 CreateGatewayServiceDto 对应 + export interface CreateServiceRequest { + servicePrefix: string; + serviceName: string; + version?: string; + serviceAddress: string; + destinationId: string; + weight?: number; + isGlobal?: boolean; + tenantCode?: string; } - export interface UpdateTenantRequest { - tenantName?: string; - contactName?: string; - contactEmail?: string; - contactPhone?: string; - maxUsers?: number; - description?: string; - status?: number; - } - - export interface Route { + // 与后端 GatewayRouteDto 对应 + export interface GatewayRoute { id: number; - tenantCode: string; serviceName: string; clusterId: string; pathPattern: string; priority: number; isGlobal: boolean; + tenantCode?: string; status: number; - metadata?: Record; - version: number; - createdTime: string; - updatedTime?: string; + instanceCount: number; } + // 与后端 CreateGatewayRouteDto 对应 export interface CreateRouteRequest { - tenantCode?: string; serviceName: string; clusterId: string; pathPattern: string; priority?: number; isGlobal?: boolean; - metadata?: Record; + tenantCode?: string; } - export interface BatchCreateRoutesRequest { - routes: CreateRouteRequest[]; - } - - export interface Cluster { - clusterId: string; - clusterName: string; - description?: string; - loadBalancingPolicy: string; - instanceCount: number; - healthyInstanceCount: number; - version: number; - createdTime: string; - updatedTime?: string; - instances: Instance[]; - } - - export interface CreateClusterRequest { - clusterId: string; - clusterName: string; - description?: string; - loadBalancingPolicy?: string; - } - - export interface Instance { + // 与后端 GatewayInstanceDto 对应 + export interface GatewayInstance { id: number; clusterId: string; destinationId: string; @@ -258,42 +181,44 @@ export namespace GatewayApi { weight: number; health: number; status: number; - version: number; - createdTime: string; - updatedTime?: string; + createdAt: string; } + // 与后端 CreateGatewayInstanceDto 对应 export interface CreateInstanceRequest { + clusterId: string; destinationId: string; address: string; weight?: number; - isHealthy?: boolean; } - export interface ConfigStatus { - routeCount: number; - clusterCount: number; - instanceCount: number; - healthyInstanceCount: number; - lastReloadTime: string; - isListening: boolean; - listenerStatus: string; + // ========== 保留旧接口兼容性 (已废弃) ========== + export interface OverviewStats extends GatewayStatistics {} + export interface Route extends GatewayRoute {} + export interface Instance extends GatewayInstance {} + export interface Tenant { + id: number; + tenantCode: string; + tenantName: string; } - - export interface VersionInfo { - routeVersion: number; - clusterVersion: number; - routeVersionUpdatedAt: string; - clusterVersionUpdatedAt: string; + export interface CreateTenantRequest { + tenantCode: string; + tenantName: string; } - - export interface OverviewStats { - totalTenants: number; - activeTenants: number; - totalRoutes: number; - totalClusters: number; - totalInstances: number; - healthyInstances: number; - lastUpdated: string; + export interface UpdateTenantRequest { + tenantName?: string; + } + export interface Cluster { + clusterId: string; + clusterName: string; + } + export interface CreateClusterRequest { + clusterId: string; + clusterName: string; + } + export interface ConfigStatus extends GatewayStatistics {} + export interface VersionInfo {} + export interface BatchCreateRoutesRequest { + routes: CreateRouteRequest[]; } } diff --git a/apps/web-ele/src/router/routes/core.ts b/apps/web-ele/src/router/routes/core.ts index 295082c..0f77026 100644 --- a/apps/web-ele/src/router/routes/core.ts +++ b/apps/web-ele/src/router/routes/core.ts @@ -35,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [ }, name: 'Root', path: '/', - redirect: preferences.app.defaultHomePath, + redirect: '/dashboard/index', children: [], }, { diff --git a/apps/web-ele/src/router/routes/modules/dashboard.ts b/apps/web-ele/src/router/routes/modules/dashboard.ts deleted file mode 100644 index 5254dc6..0000000 --- a/apps/web-ele/src/router/routes/modules/dashboard.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'lucide:layout-dashboard', - order: -1, - title: $t('page.dashboard.title'), - }, - name: 'Dashboard', - path: '/dashboard', - children: [ - { - name: 'Analytics', - path: '/analytics', - component: () => import('#/views/dashboard/analytics/index.vue'), - meta: { - affixTab: true, - icon: 'lucide:area-chart', - title: $t('page.dashboard.analytics'), - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-ele/src/router/routes/modules/demos.ts b/apps/web-ele/src/router/routes/modules/demos.ts deleted file mode 100644 index 907ea3f..0000000 --- a/apps/web-ele/src/router/routes/modules/demos.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: $t('demos.title'), - }, - name: 'Demos', - path: '/demos', - children: [ - { - meta: { - title: $t('demos.elementPlus'), - }, - name: 'NaiveDemos', - path: '/demos/element', - component: () => import('#/views/demos/element/index.vue'), - }, - { - meta: { - title: $t('demos.form'), - }, - name: 'BasicForm', - path: '/demos/form', - component: () => import('#/views/demos/form/basic.vue'), - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-ele/src/router/routes/modules/fengling.ts b/apps/web-ele/src/router/routes/modules/fengling.ts index 7caecab..600aa06 100644 --- a/apps/web-ele/src/router/routes/modules/fengling.ts +++ b/apps/web-ele/src/router/routes/modules/fengling.ts @@ -1,77 +1,140 @@ import type { RouteRecordRaw } from 'vue-router'; const routes: RouteRecordRaw[] = [ + // ========== 系统管理 ========== { meta: { - icon: 'lucide:building-2', + icon: 'lucide:settings', order: 1, - title: 'Fengling Console', + title: '系统管理', }, - name: 'Fengling', - path: '/fengling', + name: 'System', + path: '/system', children: [ - { - name: 'FenglingDashboard', - path: '/fengling/dashboard', - component: () => import('#/views/fengling/dashboard/index.vue'), - meta: { - affixTab: true, - icon: 'lucide:layout-dashboard', - title: 'Dashboard', - }, - }, { name: 'TenantManagement', - path: '/fengling/tenants', + path: '/system/tenants', component: () => import('#/views/fengling/tenants/index.vue'), meta: { icon: 'lucide:building', - title: 'Tenant Management', + title: '租户管理', }, }, { name: 'UserManagement', - path: '/fengling/users', + path: '/system/users', component: () => import('#/views/fengling/users/index.vue'), meta: { icon: 'lucide:users', - title: 'User Management', + title: '用户管理', }, }, { name: 'RoleManagement', - path: '/fengling/roles', + path: '/system/roles', component: () => import('#/views/fengling/roles/index.vue'), meta: { icon: 'lucide:shield', - title: 'Role Management', + title: '角色管理', }, }, { - name: 'OAuthClientManagement', - path: '/fengling/oauth', - component: () => import('#/views/fengling/oauth/index.vue'), - meta: { - icon: 'lucide:key', - title: 'OAuth Clients', - }, - }, - { - name: 'Logs', - path: '/fengling/logs', + name: 'SystemLogs', + path: '/system/logs', component: () => import('#/views/fengling/logs/index.vue'), meta: { icon: 'lucide:scroll-text', - title: 'Logs', + title: '日志管理', + }, + }, + ], + }, + // ========== 认证管理 ========== + { + meta: { + icon: 'lucide:shield-check', + order: 2, + title: '认证管理', + }, + name: 'Auth', + path: '/auth', + children: [ + { + name: 'OAuthClientManagement', + path: '/auth/oauth', + component: () => import('#/views/fengling/oauth/index.vue'), + meta: { + icon: 'lucide:key', + title: 'OAuth 客户端', }, }, { name: 'PointsRules', - path: '/fengling/points-rules', + path: '/auth/points-rules', component: () => import('#/views/fengling/points-rules/index.vue'), meta: { icon: 'lucide:coins', - title: 'Points Rules', + title: '积分规则', + }, + }, + ], + }, + // ========== 网关管理 ========== + { + meta: { + icon: 'lucide:network', + order: 3, + title: '网关管理', + }, + name: 'Gateway', + path: '/gateway', + children: [ + { + name: 'GatewayDashboard', + path: '/gateway/index', + component: () => import('#/views/fengling/gateway/index.vue'), + meta: { + icon: 'lucide:layout-dashboard', + title: '概览', + }, + }, + { + name: 'GatewayRoutes', + path: '/gateway/routes', + component: () => import('#/views/fengling/gateway/routes.vue'), + meta: { + icon: 'lucide:route', + title: '路由管理', + }, + }, + { + name: 'GatewayServices', + path: '/gateway/services', + component: () => import('#/views/fengling/gateway/clusters.vue'), + meta: { + icon: 'lucide:server', + title: '服务管理', + }, + }, + ], + }, + // ========== Dashboard (独立页面) ========== + { + meta: { + icon: 'lucide:layout-dashboard', + order: 0, + title: '仪表盘', + }, + name: 'Dashboard', + path: '/dashboard', + children: [ + { + name: 'FenglingDashboard', + path: '/dashboard/index', + component: () => import('#/views/fengling/dashboard/index.vue'), + meta: { + icon: 'lucide:layout-dashboard', + title: '仪表盘', }, }, ], diff --git a/apps/web-ele/src/router/routes/modules/vben.ts b/apps/web-ele/src/router/routes/modules/vben.ts index 5c522f3..878f82b 100644 --- a/apps/web-ele/src/router/routes/modules/vben.ts +++ b/apps/web-ele/src/router/routes/modules/vben.ts @@ -1,94 +1,18 @@ import type { RouteRecordRaw } from 'vue-router'; -import { - VBEN_ANT_PREVIEW_URL, - VBEN_DOC_URL, - VBEN_GITHUB_URL, - VBEN_LOGO_URL, - VBEN_NAIVE_PREVIEW_URL, - VBEN_TD_PREVIEW_URL, -} from '@vben/constants'; -import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons'; - -import { IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - const routes: RouteRecordRaw[] = [ - { - meta: { - badgeType: 'dot', - icon: VBEN_LOGO_URL, - order: 9998, - title: $t('demos.vben.title'), - }, - name: 'VbenProject', - path: '/vben-admin', - children: [ - { - name: 'VbenDocument', - path: '/vben-admin/document', - component: IFrameView, - meta: { - icon: 'lucide:book-open-text', - link: VBEN_DOC_URL, - title: $t('demos.vben.document'), - }, - }, - { - name: 'VbenGithub', - path: '/vben-admin/github', - component: IFrameView, - meta: { - icon: 'mdi:github', - link: VBEN_GITHUB_URL, - title: 'Github', - }, - }, - { - name: 'VbenNaive', - path: '/vben-admin/naive', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: 'logos:naiveui', - link: VBEN_NAIVE_PREVIEW_URL, - title: $t('demos.vben.naive-ui'), - }, - }, - { - name: 'VbenAntd', - path: '/vben-admin/antd', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: SvgAntdvLogoIcon, - link: VBEN_ANT_PREVIEW_URL, - title: $t('demos.vben.antdv'), - }, - }, - { - name: 'VbenTDesign', - path: '/vben-admin/tdesign', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: SvgTDesignIcon, - link: VBEN_TD_PREVIEW_URL, - title: $t('demos.vben.tdesign'), - }, - }, - ], - }, + // ========== 关于 ========== { name: 'VbenAbout', - path: '/vben-admin/about', + path: '/about', component: () => import('#/views/_core/about/index.vue'), meta: { icon: 'lucide:copyright', - title: $t('demos.vben.about'), + title: '关于', order: 9999, }, }, + // ========== 个人中心 (隐藏菜单) ========== { name: 'Profile', path: '/profile', @@ -96,7 +20,7 @@ const routes: RouteRecordRaw[] = [ meta: { icon: 'lucide:user', hideInMenu: true, - title: $t('page.auth.profile'), + title: '个人中心', }, }, ]; diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue deleted file mode 100644 index f1f0b23..0000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue deleted file mode 100644 index 190fb41..0000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue deleted file mode 100644 index 6ff5208..0000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue deleted file mode 100644 index 0915c7a..0000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue deleted file mode 100644 index 7e0f101..0000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/index.vue b/apps/web-ele/src/views/dashboard/analytics/index.vue deleted file mode 100644 index 5e3d6d2..0000000 --- a/apps/web-ele/src/views/dashboard/analytics/index.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/workspace/index.vue b/apps/web-ele/src/views/dashboard/workspace/index.vue deleted file mode 100644 index b95d613..0000000 --- a/apps/web-ele/src/views/dashboard/workspace/index.vue +++ /dev/null @@ -1,266 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/demos/element/index.vue b/apps/web-ele/src/views/demos/element/index.vue deleted file mode 100644 index 0a7012d..0000000 --- a/apps/web-ele/src/views/demos/element/index.vue +++ /dev/null @@ -1,117 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/demos/form/basic.vue b/apps/web-ele/src/views/demos/form/basic.vue deleted file mode 100644 index 0ecab58..0000000 --- a/apps/web-ele/src/views/demos/form/basic.vue +++ /dev/null @@ -1,191 +0,0 @@ - - diff --git a/apps/web-ele/src/views/fengling/gateway/clusters.vue b/apps/web-ele/src/views/fengling/gateway/clusters.vue index 58a80b5..05d3ac9 100644 --- a/apps/web-ele/src/views/fengling/gateway/clusters.vue +++ b/apps/web-ele/src/views/fengling/gateway/clusters.vue @@ -13,189 +13,297 @@ import { ElTag, ElPopconfirm, ElMessage, - ElCard, + ElSwitch, + ElRadioGroup, + ElRadioButton, } from 'element-plus'; -import { Plus, Edit, Delete, Refresh, View } from '@element-plus/icons-vue'; +import { Plus, Refresh, View } from '@element-plus/icons-vue'; -import { GatewayApi, type GatewayApi as GApi } from '#/api/fengling/gateway'; +import { GatewayApi } from '#/api/fengling/gateway'; const loading = ref(false); -const tableData = ref([]); -const dialogVisible = ref(false); -const dialogTitle = ref('Create Cluster'); -const formData = ref>({}); -const selectedCluster = ref(null); +const servicesLoading = ref(false); +const services = ref([]); +const instances = ref([]); +const serviceDialogVisible = ref(false); const instanceDialogVisible = ref(false); +const selectedService = ref(null); -const loadClusters = async () => { +const serviceForm = ref>({}); +const instanceForm = ref>({}); +const globalOnly = ref(false); + +const loadServices = async () => { loading.value = true; try { - const response = await GatewayApi.getClusterList(); - tableData.value = response || []; + const response = await GatewayApi.getServices({ globalOnly: globalOnly.value }); + services.value = response || []; } catch (error: any) { - console.error('[Gateway] Error loading clusters:', error); - ElMessage.error(error?.response?.data?.message || 'Failed to load clusters'); + console.error('[Gateway] Error loading services:', error); + ElMessage.error(error?.response?.data?.message || 'Failed to load services'); } finally { loading.value = false; } }; -const handleCreate = () => { - dialogTitle.value = 'Create Cluster'; - formData.value = { - loadBalancingPolicy: 'DistributedWeightedRoundRobin', - }; - dialogVisible.value = true; -}; - -const handleEdit = (row: GApi.Cluster) => { - dialogTitle.value = 'Edit Cluster'; - formData.value = { ...row }; - dialogVisible.value = true; -}; - -const handleDelete = async (clusterId: string) => { +const loadInstances = async (clusterId: string) => { + instances.value = []; + servicesLoading.value = true; try { - await GatewayApi.deleteCluster(clusterId); - ElMessage.success('Cluster deleted successfully'); - await loadClusters(); + const response = await GatewayApi.getInstances(clusterId); + instances.value = response || []; } catch (error: any) { - ElMessage.error(error?.response?.data?.message || 'Failed to delete cluster'); + console.error('[Gateway] Error loading instances:', error); + ElMessage.error(error?.response?.data?.message || 'Failed to load instances'); + } finally { + servicesLoading.value = false; } }; -const handleViewInstances = async (row: GApi.Cluster) => { - selectedCluster.value = row; +const handleCreateService = () => { + serviceForm.value = { + isGlobal: true, + weight: 1, + version: 'v1', + }; + serviceDialogVisible.value = true; +}; + +const handleViewInstances = async (row: GatewayApi.GatewayService) => { + selectedService.value = row; + await loadInstances(row.clusterId); instanceDialogVisible.value = true; }; -const handleSubmit = async () => { +const handleDeleteService = async (serviceName: string, tenantCode?: string) => { try { - if (formData.value.clusterId) { - await GatewayApi.updateCluster(formData.value.clusterId, formData.value as GApi.CreateClusterRequest); - ElMessage.success('Cluster updated successfully'); - } else { - await GatewayApi.createCluster(formData.value as GApi.CreateClusterRequest); - ElMessage.success('Cluster created successfully'); - } - dialogVisible.value = false; - await loadClusters(); + await GatewayApi.deleteService(serviceName, tenantCode); + ElMessage.success('Service deleted successfully'); + await loadServices(); + } catch (error: any) { + ElMessage.error(error?.response?.data?.message || 'Failed to delete service'); + } +}; + +const handleSubmitService = async () => { + try { + await GatewayApi.createService(serviceForm.value as GatewayApi.CreateServiceRequest); + ElMessage.success('Service created successfully'); + serviceDialogVisible.value = false; + await loadServices(); } catch (error: any) { ElMessage.error(error?.response?.data?.message || 'Operation failed'); } }; -const handleDeleteInstance = async (id: number) => { +const handleCreateInstance = () => { + if (!selectedService.value) return; + instanceForm.value = { + clusterId: selectedService.value.clusterId, + weight: 1, + }; + // 创建一个临时的 dialog 来添加实例 + instanceDialogVisible.value = true; +}; + +const handleSubmitInstance = async () => { try { - await GatewayApi.deleteInstance(id); + await GatewayApi.createInstance(instanceForm.value as GatewayApi.CreateInstanceRequest); + ElMessage.success('Instance created successfully'); + if (selectedService.value) { + await loadInstances(selectedService.value.clusterId); + } + } catch (error: any) { + ElMessage.error(error?.response?.data?.message || 'Operation failed'); + } +}; + +const handleDeleteInstance = async (instanceId: number) => { + try { + await GatewayApi.deleteInstance(instanceId); ElMessage.success('Instance deleted successfully'); - if (selectedCluster.value) { - const updated = await GatewayApi.getClusterById(selectedCluster.value.clusterId); - selectedCluster.value = updated; + if (selectedService.value) { + await loadInstances(selectedService.value.clusterId); } } catch (error: any) { ElMessage.error(error?.response?.data?.message || 'Failed to delete instance'); } }; +const handleGlobalFilterChange = (value: boolean | string) => { + globalOnly.value = value === true; + loadServices(); +}; + +const getStatusTag = (status: number) => { + return status === 1 ? 'success' : 'danger'; +}; + +const getStatusText = (status: number) => { + return status === 1 ? '启用' : '禁用'; +}; + const getHealthTag = (health: number) => { return health === 1 ? 'success' : 'danger'; }; const getHealthText = (health: number) => { - return health === 1 ? 'Healthy' : 'Unhealthy'; + return health === 1 ? '健康' : '不健康'; }; onMounted(() => { - loadClusters(); + loadServices(); }); @@ -207,6 +315,8 @@ onMounted(() => { .toolbar { margin-bottom: 20px; + display: flex; + align-items: center; } .cluster-info { diff --git a/apps/web-ele/src/views/fengling/gateway/index.vue b/apps/web-ele/src/views/fengling/gateway/index.vue index 3b948cb..a1796d8 100644 --- a/apps/web-ele/src/views/fengling/gateway/index.vue +++ b/apps/web-ele/src/views/fengling/gateway/index.vue @@ -1,24 +1,19 @@