feat(admin): 优化后台运维与任务管理
完善平台、基准模型、定价规则和实时负载的紧凑查询与滚动展示,并固定关键操作列。 新增管理员任务记录、批量计费结算和用户组限流、额度及模型权限维护能力,同时补充任务脱敏、查询索引与 OpenAPI 契约。 验证:pnpm openapi、Go 全量测试、pnpm lint、pnpm test、pnpm build、gofmt 与差异格式检查均通过。
This commit is contained in:
+69
-7
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user