完善 API Key 能力范围可视化和维护

This commit is contained in:
2026-06-07 19:01:32 +08:00
parent dc14866210
commit f47132a653
19 changed files with 1165 additions and 49 deletions
+45 -10
View File
@@ -10,6 +10,7 @@ import type {
GatewayAccessRule,
GatewayAccessRuleUpsertRequest,
GatewayApiKey,
GatewayApiKeyScopeUpdateRequest,
GatewayAuditLog,
GatewayNetworkProxyConfig,
GatewayRunnerPolicy,
@@ -31,7 +32,7 @@ import type {
RuntimePolicySet,
UserGroupUpsertRequest,
UserGroup,
WalletBalanceAdjustmentRequest,
WalletRechargeRequest,
} from '@easyai-ai-gateway/contracts';
import {
batchAccessRules,
@@ -53,6 +54,7 @@ import {
GatewayApiError,
getHealth,
listFileStorageChannels,
getCurrentUser,
getFileStorageSettings,
getNetworkProxyConfig,
getRunnerPolicy,
@@ -63,6 +65,7 @@ import {
listApiKeys,
listBaseModels,
listCatalogProviders,
listCurrentUserGroups,
listModelCatalog,
listModelRateLimitStatuses,
listModels,
@@ -83,11 +86,12 @@ import {
loginLocalAccount,
pollTaskUntilSettled,
registerLocalAccount,
rechargeUserWalletBalance,
replacePlatformModels,
restoreModelRuntimeStatus,
setUserWalletBalance,
type HealthResponse,
updateAccessRule,
updateApiKeyScopes,
updateFileStorageChannel,
updateFileStorageSettings,
updateGatewayUser,
@@ -142,6 +146,8 @@ import type {
type DataKey =
| 'health'
| 'currentUser'
| 'currentUserGroups'
| 'publicCatalog'
| 'playgroundApiKeys'
| 'playgroundModels'
@@ -184,6 +190,8 @@ export function App() {
const [loginForm, setLoginForm] = useState<LoginForm>({ account: '', password: '' });
const [registerForm, setRegisterForm] = useState<RegisterForm>({ username: '', email: '', password: '', displayName: '', invitationCode: '' });
const [health, setHealth] = useState<HealthResponse | null>(null);
const [currentUser, setCurrentUser] = useState<ConsoleData['currentUser']>(null);
const [currentUserGroups, setCurrentUserGroups] = useState<UserGroup[]>([]);
const [platforms, setPlatforms] = useState<IntegrationPlatform[]>([]);
const [models, setModels] = useState<PlatformModel[]>([]);
const [modelCatalog, setModelCatalog] = useState<ModelCatalogResponse>({
@@ -313,6 +321,8 @@ export function App() {
auditLogs,
apiKeys,
baseModels,
currentUser,
currentUserGroups,
fileStorageChannels,
fileStorageSettings,
modelCatalog,
@@ -334,7 +344,7 @@ export function App() {
users,
walletAccounts,
walletTransactions,
}), [accessRules, apiKeys, auditLogs, baseModels, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
}), [accessRules, apiKeys, auditLogs, baseModels, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
async function refresh(nextToken = token) {
await ensureRouteData(nextToken, true);
@@ -387,6 +397,12 @@ export function App() {
setHealth(await getHealth());
return;
}
case 'currentUser':
setCurrentUser(await getCurrentUser(nextToken));
return;
case 'currentUserGroups':
setCurrentUserGroups((await listCurrentUserGroups(nextToken)).items);
return;
case 'publicCatalog': {
const [providersResult, baseModelsResult] = await Promise.all([
listPublicCatalogProviders(),
@@ -541,7 +557,7 @@ export function App() {
try {
const response = await createApiKey(token, {
name: apiKeyForm.name,
scopes: ['chat', 'embedding', 'rerank', 'image', 'video'],
scopes: ['chat', 'embedding', 'rerank', 'image', 'video', 'music', 'audio'],
expiresAt: apiKeyForm.expiresAt ? new Date(apiKeyForm.expiresAt).toISOString() : undefined,
});
setApiKeySecret(response.secret);
@@ -743,11 +759,11 @@ export function App() {
}
}
async function saveUserWalletBalance(userId: string, input: WalletBalanceAdjustmentRequest) {
async function rechargeUserWallet(userId: string, input: WalletRechargeRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const response = await setUserWalletBalance(token, userId, input);
const response = await rechargeUserWalletBalance(token, userId, input);
setUsers((current) => current.map((user) => user.id === userId
? {
...user,
@@ -760,10 +776,10 @@ export function App() {
setAuditLogs((current) => [response.auditLog, ...current.filter((item) => item.id !== response.auditLog.id)]);
invalidateDataKeys('auditLogs');
setCoreState('ready');
setCoreMessage('用户余额已更新,审计日志已记录。');
setCoreMessage('用户余额已充值,审计日志已记录。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '更新用户余额失败');
setCoreMessage(err instanceof Error ? err.message : '充值用户余额失败');
throw err;
}
}
@@ -840,6 +856,22 @@ export function App() {
}
}
async function saveAPIKeyScopes(apiKeyId: string, input: GatewayApiKeyScopeUpdateRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const item = await updateApiKeyScopes(token, apiKeyId, input);
setApiKeys((current) => current.map((apiKey) => (apiKey.id === item.id ? { ...apiKey, ...item } : apiKey)));
invalidateDataKeys('playgroundApiKeys', 'playgroundModels', 'modelCatalog');
setCoreState('ready');
setCoreMessage('API Key 能力范围已更新。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '更新 API Key 能力范围失败');
throw err;
}
}
async function saveAccessRule(input: GatewayAccessRuleUpsertRequest, ruleId?: string) {
setCoreState('loading');
setCoreMessage('');
@@ -988,6 +1020,8 @@ export function App() {
loadedDataKeysRef.current = new Set(health ? ['health'] : []);
loadingDataKeysRef.current.clear();
setState('idle');
setCurrentUser(null);
setCurrentUserGroups([]);
setPlatforms([]);
setModels([]);
setModelCatalog({ items: [], filters: { capabilities: [], providers: [] }, summary: { modelCount: 0, sourceCount: 0 } });
@@ -1143,6 +1177,7 @@ export function App() {
onDeleteApiKey={removeAPIKey}
onApiKeyFormChange={setApiKeyForm}
onSectionChange={navigateWorkspaceSection}
onSaveApiKeyScopes={saveAPIKeyScopes}
onSubmitApiKey={submitAPIKey}
onTaskQueryChange={navigateWorkspaceTaskQuery}
onTransactionQueryChange={setWorkspaceTransactionQuery}
@@ -1200,7 +1235,7 @@ export function App() {
onSaveFileStorageSettings={saveFileStorageSettings}
onSaveTenant={saveTenant}
onSaveUser={saveUser}
onSetUserWalletBalance={saveUserWalletBalance}
onRechargeUserWalletBalance={rechargeUserWallet}
onSaveUserGroup={saveUserGroup}
onClearOperationMessage={() => setCoreMessage('')}
onSectionChange={navigateAdminSection}
@@ -1382,7 +1417,7 @@ function dataKeysForRoute(
if (!isAuthenticated) return [];
if (activePage === 'workspace') {
if (workspaceSection === 'overview') return ['users', 'userGroups', 'apiKeys'];
if (workspaceSection === 'overview') return ['currentUser', 'currentUserGroups', 'apiKeys'];
if (workspaceSection === 'billing') return ['wallet'];
if (workspaceSection === 'apiKeys') return ['apiKeys', 'accessRules', 'playgroundModels'];
if (workspaceSection === 'tasks') return ['tasks'];