Show admin save feedback and preserve custom failover actions

This commit is contained in:
wangbo 2026-05-12 21:57:10 +08:00
parent a76ab941bc
commit e778fad6ed
3 changed files with 22 additions and 7 deletions

View File

@ -1058,6 +1058,7 @@ export function App() {
onSaveUser={saveUser}
onSetUserWalletBalance={saveUserWalletBalance}
onSaveUserGroup={saveUserGroup}
onClearOperationMessage={() => setCoreMessage('')}
onSectionChange={navigateAdminSection}
/>
) : (

View File

@ -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<void>;
onSetUserWalletBalance: (userId: string, input: WalletBalanceAdjustmentRequest) => Promise<void>;
onSaveUserGroup: (input: UserGroupUpsertRequest, groupId?: string) => Promise<void>;
onClearOperationMessage: () => void;
onSectionChange: (value: AdminSection) => void;
}) {
return (
<div className="pageStack">
<ScreenMessage
message={props.operationMessage}
variant={props.state === 'error' ? 'error' : 'success'}
onClose={props.onClearOperationMessage}
/>
<div className="subPageLayout">
<Tabs className="sideTabs" value={props.section} tabs={tabs} onValueChange={props.onSectionChange} />
<div className="subPageContent">

View File

@ -76,6 +76,8 @@ const failoverActionDefinitions = [
},
] as const;
const failoverActionValues = new Set<string>(failoverActionDefinitions.map((item) => item.value));
const failoverCategoryOptions = [
'network',
'timeout',
@ -653,19 +655,18 @@ function defaultFailoverActions(): Record<string, unknown> {
}
function failoverCategoryRoutingGroups(allowCategories: string[], denyCategories: string[], actions: Record<string, unknown>) {
const actionSet = new Set(failoverActionDefinitions.map((item) => item.value));
const assignments = new Map<string, string>();
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),