feat(admin): 优化后台运维与任务管理

完善平台、基准模型、定价规则和实时负载的紧凑查询与滚动展示,并固定关键操作列。

新增管理员任务记录、批量计费结算和用户组限流、额度及模型权限维护能力,同时补充任务脱敏、查询索引与 OpenAPI 契约。

验证:pnpm openapi、Go 全量测试、pnpm lint、pnpm test、pnpm build、gofmt 与差异格式检查均通过。
This commit is contained in:
2026-07-25 01:32:49 +08:00
parent b29d539d49
commit de10875439
38 changed files with 5015 additions and 425 deletions
+69 -7
View File
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
import type {
AdminGatewayTask,
BaseModelCatalogItem,
CatalogProvider,
ClientCustomizationSettings,
@@ -67,6 +68,7 @@ import {
getSecurityEventConnection,
getWalletSummary,
listAccessRules,
listAdminTasks,
listAuditLogs,
listApiKeyAccessRules,
listApiKeyAssignableModels,
@@ -141,7 +143,9 @@ import { ModelsPage } from './pages/ModelsPage';
import { PlaygroundPage } from './pages/PlaygroundPage';
import { WorkspacePage } from './pages/WorkspacePage';
import {
adminTaskQueryKey,
parseAppRoute,
pathForAdminTaskQuery,
pathForAdminSection,
pathForApiDocSection,
pathForPage,
@@ -153,6 +157,7 @@ import {
} from './routing';
import type {
AdminSection,
AdminTaskQuery,
ApiDocSection,
ApiKeyForm,
AuthMode,
@@ -197,6 +202,7 @@ type DataKey =
| 'tenants'
| 'users'
| 'userGroups'
| 'adminTasks'
| 'tasks'
| 'wallet'
| 'walletTransactions'
@@ -208,6 +214,7 @@ export function App() {
const initialRoute = parseAppRoute();
const [activePage, setActivePage] = useState<PageKey>(initialRoute.activePage);
const [adminSection, setAdminSection] = useState<AdminSection>(initialRoute.adminSection);
const [adminTaskQuery, setAdminTaskQuery] = useState<AdminTaskQuery>(initialRoute.adminTaskQuery);
const [workspaceSection, setWorkspaceSection] = useState<WorkspaceSection>(initialRoute.workspaceSection);
const [workspaceTaskQuery, setWorkspaceTaskQuery] = useState<WorkspaceTaskQuery>(initialRoute.workspaceTaskQuery);
const [workspaceTransactionQuery, setWorkspaceTransactionQuery] = useState<WorkspaceTransactionQuery>(() => defaultWorkspaceTransactionQuery());
@@ -258,6 +265,10 @@ export function App() {
const [taskResult, setTaskResult] = useState<GatewayTask | null>(null);
const [tasks, setTasks] = useState<GatewayTask[]>([]);
const [taskTotal, setTaskTotal] = useState(0);
const [adminTasks, setAdminTasks] = useState<AdminGatewayTask[]>([]);
const [adminTaskTotal, setAdminTaskTotal] = useState(0);
const [adminTasksUpdatedAt, setAdminTasksUpdatedAt] = useState<number | null>(null);
const [adminTaskState, setAdminTaskState] = useState<LoadState>('idle');
const [walletAccounts, setWalletAccounts] = useState<GatewayWalletAccount[]>([]);
const [walletTransactions, setWalletTransactions] = useState<GatewayWalletTransaction[]>([]);
const [walletTransactionTotal, setWalletTransactionTotal] = useState(0);
@@ -279,6 +290,8 @@ export function App() {
const loadingDataKeysRef = useRef(new Set<DataKey>());
const loadedTaskQueryKeyRef = useRef('');
const currentTaskQueryKeyRef = useRef('');
const loadedAdminTaskQueryKeyRef = useRef('');
const currentAdminTaskQueryKeyRef = useRef('');
const loadedTransactionQueryKeyRef = useRef('');
const currentTransactionQueryKeyRef = useRef('');
const { removeBaseModel, removeProvider, resetAllBaseModelsToDefault, resetBaseModelToDefault, saveBaseModel, saveProvider } = useCatalogOperations({
@@ -302,8 +315,10 @@ export function App() {
token,
});
const taskListRequestKey = workspaceTaskQueryKey(workspaceTaskQuery);
const adminTaskListRequestKey = adminTaskQueryKey(adminTaskQuery);
const transactionListRequestKey = workspaceTransactionQueryKey(workspaceTransactionQuery);
currentTaskQueryKeyRef.current = taskListRequestKey;
currentAdminTaskQueryKeyRef.current = adminTaskListRequestKey;
currentTransactionQueryKeyRef.current = transactionListRequestKey;
useEffect(() => {
@@ -360,7 +375,7 @@ export function App() {
}, []);
useEffect(() => {
void ensureRouteData(token);
}, [activePage, adminSection, taskListRequestKey, transactionListRequestKey, workspaceSection, token]);
}, [activePage, adminSection, adminTaskListRequestKey, taskListRequestKey, transactionListRequestKey, workspaceSection, token]);
useEffect(() => {
if (!token || activePage !== 'admin' || adminSection !== 'realtimeLoad') return undefined;
const timer = window.setInterval(() => {
@@ -409,6 +424,7 @@ export function App() {
const data = useMemo<ConsoleData>(() => ({
accessRules,
adminTasks,
auditLogs,
apiKeys,
baseModels,
@@ -437,7 +453,7 @@ export function App() {
users,
walletAccounts,
walletTransactions,
}), [accessRules, apiKeys, auditLogs, baseModels, clientCustomizationSettings, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, securityEventConnection, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
}), [accessRules, adminTasks, apiKeys, auditLogs, baseModels, clientCustomizationSettings, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, securityEventConnection, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
async function refresh(nextToken = token) {
await ensureRouteData(nextToken, true);
@@ -452,6 +468,10 @@ export function App() {
loadedDataKeysRef.current.delete('tasks');
loadingDataKeysRef.current.delete('tasks');
}
if (activePage === 'admin' && adminSection === 'tasks' && loadedAdminTaskQueryKeyRef.current !== adminTaskListRequestKey) {
loadedDataKeysRef.current.delete('adminTasks');
loadingDataKeysRef.current.delete('adminTasks');
}
if (activePage === 'workspace' && workspaceSection === 'transactions' && loadedTransactionQueryKeyRef.current !== transactionListRequestKey) {
loadedDataKeysRef.current.delete('walletTransactions');
loadingDataKeysRef.current.delete('walletTransactions');
@@ -590,6 +610,26 @@ export function App() {
loadedTaskQueryKeyRef.current = requestKey;
}
return;
case 'adminTasks':
{
const requestKey = adminTaskListRequestKey;
setAdminTaskState('loading');
try {
const response = await listAdminTasks(nextToken, adminTaskQuery);
if (requestKey !== currentAdminTaskQueryKeyRef.current) return;
setAdminTasks(response.items);
setAdminTaskTotal(response.total ?? response.items.length);
setAdminTasksUpdatedAt(Date.now());
loadedAdminTaskQueryKeyRef.current = requestKey;
setAdminTaskState('ready');
} catch (err) {
if (requestKey === currentAdminTaskQueryKeyRef.current) {
setAdminTaskState('error');
}
throw err;
}
}
return;
case 'wallet': {
const response = await getWalletSummary(nextToken);
setWalletAccounts(response.accounts);
@@ -1219,6 +1259,10 @@ export function App() {
setTaskResult(null);
setTasks([]);
setTaskTotal(0);
setAdminTasks([]);
setAdminTaskTotal(0);
setAdminTasksUpdatedAt(null);
setAdminTaskState('idle');
setWalletAccounts([]);
setWalletTransactions([]);
setWalletTransactionTotal(0);
@@ -1270,12 +1314,13 @@ export function App() {
}
function currentRouteState(): AppRouteState {
return { activePage, adminSection, apiDocSection, playgroundMode, workspaceSection, workspaceTaskQuery };
return { activePage, adminSection, adminTaskQuery, apiDocSection, playgroundMode, workspaceSection, workspaceTaskQuery };
}
function applyRoute(route: AppRouteState) {
setActivePage(route.activePage);
setAdminSection(route.adminSection);
setAdminTaskQuery(route.adminTaskQuery);
setApiDocSection(route.apiDocSection);
setPlaygroundMode(route.playgroundMode);
setWorkspaceSection(route.workspaceSection);
@@ -1298,6 +1343,15 @@ export function App() {
navigatePath(pathForAdminSection(section));
}
function navigateAdminTaskQuery(query: AdminTaskQuery) {
navigatePath(pathForAdminTaskQuery(query));
}
async function refreshAdminTasks() {
loadedDataKeysRef.current.delete('adminTasks');
await ensureData(['adminTasks'], token, true);
}
function navigateWorkspaceSection(section: WorkspaceSection) {
navigatePath(pathForWorkspaceSection(section));
}
@@ -1405,7 +1459,11 @@ export function App() {
{activePage === 'admin' && (
isAuthenticated ? (
<AdminPage
token={token}
token={token}
adminTaskQuery={adminTaskQuery}
adminTaskTotal={adminTaskTotal}
adminTasksUpdatedAt={adminTasksUpdatedAt}
adminTaskState={adminTaskState}
data={data}
operationMessage={coreMessage}
section={adminSection}
@@ -1447,6 +1505,8 @@ export function App() {
onRechargeUserWalletBalance={rechargeUserWallet}
onSaveUserGroup={saveUserGroup}
onClearOperationMessage={() => setCoreMessage('')}
onAdminTaskQueryChange={navigateAdminTaskQuery}
onRefreshAdminTasks={refreshAdminTasks}
onSectionChange={navigateAdminSection}
/>
) : (
@@ -1647,22 +1707,24 @@ function dataKeysForRoute(
return ['pricingRuleSets'];
case 'runtime':
return ['runtimePolicySets', 'runnerPolicy'];
case 'billingSettlements':
return [];
case 'baseModels':
return ['baseModels', 'providers', 'pricingRuleSets', 'runtimePolicySets'];
case 'platforms':
return ['platforms', 'models', 'providers', 'baseModels', 'pricingRuleSets', 'networkProxyConfig'];
case 'realtimeLoad':
return ['platforms', 'modelRateLimits'];
case 'tasks':
return ['adminTasks', 'tenants', 'users', 'userGroups', 'platforms', 'models'];
case 'tenants':
return ['tenants', 'userGroups'];
case 'users':
return ['users', 'tenants', 'userGroups'];
case 'userGroups':
return ['userGroups'];
return ['userGroups', 'accessRules', 'platforms', 'models'];
case 'auditLogs':
return ['auditLogs'];
case 'accessRules':
return ['accessRules', 'userGroups', 'platforms', 'models'];
case 'systemSettings':
return ['clientCustomizationSettings', 'fileStorageSettings', 'fileStorageChannels', 'securityEventConnection'];
default: