From e778fad6ed8250bafb6d3f03172b1aaf54eb3b8c Mon Sep 17 00:00:00 2001 From: wangbo Date: Tue, 12 May 2026 21:57:10 +0800 Subject: [PATCH] Show admin save feedback and preserve custom failover actions --- apps/web/src/App.tsx | 1 + apps/web/src/pages/AdminPage.tsx | 8 +++++++- .../src/pages/admin/RuntimePoliciesPanel.tsx | 20 +++++++++++++------ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index f353298..2d508af 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1058,6 +1058,7 @@ export function App() { onSaveUser={saveUser} onSetUserWalletBalance={saveUserWalletBalance} onSaveUserGroup={saveUserGroup} + onClearOperationMessage={() => setCoreMessage('')} onSectionChange={navigateAdminSection} /> ) : ( diff --git a/apps/web/src/pages/AdminPage.tsx b/apps/web/src/pages/AdminPage.tsx index b7ccb3c..0866033 100644 --- a/apps/web/src/pages/AdminPage.tsx +++ b/apps/web/src/pages/AdminPage.tsx @@ -18,7 +18,7 @@ import type { import type { ConsoleData, StatItem } from '../app-state'; import { EntityTable } from '../components/EntityTable'; import { StatGrid } from '../components/StatGrid'; -import { Badge, Card, CardContent, CardHeader, CardTitle, Tabs } from '../components/ui'; +import { Badge, Card, CardContent, CardHeader, CardTitle, ScreenMessage, Tabs } from '../components/ui'; import type { AdminSection, LoadState, PlatformWithModelsInput } from '../types'; import { AccessRulesPanel } from './admin/AccessRulesPanel'; import { AuditLogsPanel } from './admin/AuditLogsPanel'; @@ -76,10 +76,16 @@ export function AdminPage(props: { onSaveUser: (input: GatewayUserUpsertRequest, userId?: string) => Promise; onSetUserWalletBalance: (userId: string, input: WalletBalanceAdjustmentRequest) => Promise; onSaveUserGroup: (input: UserGroupUpsertRequest, groupId?: string) => Promise; + onClearOperationMessage: () => void; onSectionChange: (value: AdminSection) => void; }) { return (
+
diff --git a/apps/web/src/pages/admin/RuntimePoliciesPanel.tsx b/apps/web/src/pages/admin/RuntimePoliciesPanel.tsx index 3a98ed5..48dcb4d 100644 --- a/apps/web/src/pages/admin/RuntimePoliciesPanel.tsx +++ b/apps/web/src/pages/admin/RuntimePoliciesPanel.tsx @@ -76,6 +76,8 @@ const failoverActionDefinitions = [ }, ] as const; +const failoverActionValues = new Set(failoverActionDefinitions.map((item) => item.value)); + const failoverCategoryOptions = [ 'network', 'timeout', @@ -653,19 +655,18 @@ function defaultFailoverActions(): Record { } function failoverCategoryRoutingGroups(allowCategories: string[], denyCategories: string[], actions: Record) { - const actionSet = new Set(failoverActionDefinitions.map((item) => item.value)); const assignments = new Map(); for (const category of cleanTags(allowCategories)) { const rawAction = actions[category]; const action = typeof rawAction === 'string' ? rawAction : ''; - assignments.set(category, actionSet.has(action as (typeof failoverActionDefinitions)[number]['value']) ? action : 'next'); + assignments.set(category, failoverActionValues.has(action) ? action : 'next'); } for (const [category, rawAction] of Object.entries(actions)) { if (assignments.has(category)) continue; const action = typeof rawAction === 'string' ? rawAction : ''; if (action === 'stop') { assignments.set(category, 'deny'); - } else if (actionSet.has(action as (typeof failoverActionDefinitions)[number]['value'])) { + } else if (failoverActionValues.has(action)) { assignments.set(category, action); } } @@ -708,9 +709,16 @@ function updateFailoverCategoryRouting( knownCategories.add(category); } for (const [category, rawAction] of Object.entries(actions)) { - if (!knownCategories.has(category) && typeof rawAction === 'string' && rawAction !== 'next' && rawAction !== 'stop') { - nextActions[category] = rawAction; - } + const shouldPreserveCustomAction = ( + !knownCategories.has(category) + && typeof rawAction === 'string' + && rawAction !== 'next' + && rawAction !== 'stop' + && !failoverActionValues.has(rawAction) + ); + if (!shouldPreserveCustomAction) continue; + // Keep forward-compatible custom actions that this editor cannot render. + nextActions[category] = rawAction; } return { allowCategories: cleanTags(nextAllowCategories),