fix(access): 修正 API Key 可分配模型范围
This commit is contained in:
+21
-14
@@ -69,6 +69,7 @@ import {
|
||||
listAccessRules,
|
||||
listAuditLogs,
|
||||
listApiKeyAccessRules,
|
||||
listApiKeyAssignableModels,
|
||||
listApiKeys,
|
||||
listBaseModels,
|
||||
listCatalogProviders,
|
||||
@@ -176,6 +177,7 @@ type DataKey =
|
||||
| 'publicCatalog'
|
||||
| 'playgroundApiKeys'
|
||||
| 'playgroundModels'
|
||||
| 'apiKeyPolicyModels'
|
||||
| 'modelCatalog'
|
||||
| 'networkProxyConfig'
|
||||
| 'clientCustomizationSettings'
|
||||
@@ -227,6 +229,7 @@ export function App() {
|
||||
summary: { modelCount: 0, sourceCount: 0 },
|
||||
});
|
||||
const [playgroundModels, setPlaygroundModels] = useState<PlatformModel[]>([]);
|
||||
const [apiKeyPolicyModels, setApiKeyPolicyModels] = useState<PlatformModel[]>([]);
|
||||
const [networkProxyConfig, setNetworkProxyConfig] = useState<GatewayNetworkProxyConfig | null>(null);
|
||||
const [clientCustomizationSettings, setClientCustomizationSettings] = useState<ClientCustomizationSettings | null>(null);
|
||||
const [fileStorageChannels, setFileStorageChannels] = useState<FileStorageChannel[]>([]);
|
||||
@@ -530,6 +533,9 @@ export function App() {
|
||||
case 'playgroundModels':
|
||||
setPlaygroundModels((await listPlayableModels(nextToken)).items);
|
||||
return;
|
||||
case 'apiKeyPolicyModels':
|
||||
setApiKeyPolicyModels((await listApiKeyAssignableModels(nextToken)).items);
|
||||
return;
|
||||
case 'playgroundApiKeys': {
|
||||
const response = await listPlayableApiKeys(nextToken);
|
||||
setApiKeys(response.items);
|
||||
@@ -687,7 +693,7 @@ export function App() {
|
||||
const modelsResponse = await replacePlatformModels(token, platform.id, modelBindings);
|
||||
setPlatforms((current) => [platformForState, ...current.filter((item) => item.id !== platform.id)]);
|
||||
setModels((current) => [...current.filter((model) => model.platformId !== platform.id), ...modelsResponse.items]);
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits');
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(input.platformId
|
||||
? `平台已更新,当前绑定 ${input.models.length} 个模型。`
|
||||
@@ -707,7 +713,7 @@ export function App() {
|
||||
const updated = await updatePlatform(token, platform.id, input);
|
||||
const platformForState = withCredentialPreviewFallback(updated, input, platform);
|
||||
setPlatforms((current) => current.map((item) => item.id === platform.id ? platformForState : item));
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'playgroundModels');
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(status === 'enabled' ? '平台已启用。' : '平台已禁用。');
|
||||
} catch (err) {
|
||||
@@ -739,7 +745,7 @@ export function App() {
|
||||
platformPriority: state.priority,
|
||||
}
|
||||
: status));
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'platforms', 'playgroundModels');
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'platforms', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(input.reset ? '平台动态优先级已重置。' : '平台动态优先级已更新。');
|
||||
} catch (err) {
|
||||
@@ -783,7 +789,7 @@ export function App() {
|
||||
cooldownUntil: undefined,
|
||||
}
|
||||
: model));
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'models', 'platforms', 'playgroundModels');
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'models', 'platforms', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('模型运行状态已恢复。');
|
||||
} catch (err) {
|
||||
@@ -800,7 +806,7 @@ export function App() {
|
||||
await deletePlatform(token, platformId);
|
||||
setPlatforms((current) => current.filter((item) => item.id !== platformId));
|
||||
setModels((current) => current.filter((item) => item.platformId !== platformId));
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits');
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('平台已删除。');
|
||||
} catch (err) {
|
||||
@@ -816,6 +822,7 @@ export function App() {
|
||||
try {
|
||||
const item = tenantId ? await updateTenant(token, tenantId, input) : await createTenant(token, input);
|
||||
setTenants((current) => [item, ...current.filter((tenant) => tenant.id !== item.id)]);
|
||||
invalidateDataKeys('playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(tenantId ? '租户已更新。' : '租户已创建。');
|
||||
} catch (err) {
|
||||
@@ -831,6 +838,7 @@ export function App() {
|
||||
try {
|
||||
await deleteTenant(token, tenantId);
|
||||
setTenants((current) => current.filter((tenant) => tenant.id !== tenantId));
|
||||
invalidateDataKeys('playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('租户已删除。');
|
||||
} catch (err) {
|
||||
@@ -846,7 +854,7 @@ export function App() {
|
||||
try {
|
||||
const item = userId ? await updateGatewayUser(token, userId, input) : await createGatewayUser(token, input);
|
||||
setUsers((current) => [item, ...current.filter((user) => user.id !== item.id)]);
|
||||
invalidateDataKeys('playgroundModels');
|
||||
invalidateDataKeys('playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(userId ? '用户已更新。' : '用户已创建。');
|
||||
} catch (err) {
|
||||
@@ -902,7 +910,7 @@ export function App() {
|
||||
try {
|
||||
const item = groupId ? await updateUserGroup(token, groupId, input) : await createUserGroup(token, input);
|
||||
setUserGroups((current) => [item, ...current.filter((group) => group.id !== item.id)]);
|
||||
invalidateDataKeys('modelCatalog');
|
||||
invalidateDataKeys('modelCatalog', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(groupId ? '用户组已更新。' : '用户组已创建。');
|
||||
} catch (err) {
|
||||
@@ -920,8 +928,7 @@ export function App() {
|
||||
setUserGroups((current) => current.filter((group) => group.id !== groupId));
|
||||
setTenants((current) => current.map((tenant) => tenant.defaultUserGroupId === groupId ? { ...tenant, defaultUserGroupId: undefined } : tenant));
|
||||
setUsers((current) => current.map((user) => user.defaultUserGroupId === groupId ? { ...user, defaultUserGroupId: undefined } : user));
|
||||
invalidateDataKeys('modelCatalog');
|
||||
invalidateDataKeys('playgroundModels');
|
||||
invalidateDataKeys('modelCatalog', 'playgroundModels', 'apiKeyPolicyModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('用户组已删除。');
|
||||
} catch (err) {
|
||||
@@ -975,7 +982,7 @@ export function App() {
|
||||
try {
|
||||
const item = ruleId ? await updateAccessRule(token, ruleId, input) : await createAccessRule(token, input);
|
||||
setAccessRules((current) => [item, ...current.filter((rule) => rule.id !== item.id)]);
|
||||
invalidateDataKeys('playgroundModels', 'modelCatalog');
|
||||
invalidateDataKeys('playgroundModels', 'apiKeyPolicyModels', 'modelCatalog');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(ruleId ? '访问权限规则已更新。' : '访问权限规则已创建。');
|
||||
} catch (err) {
|
||||
@@ -991,7 +998,7 @@ export function App() {
|
||||
try {
|
||||
await deleteAccessRule(token, ruleId);
|
||||
setAccessRules((current) => current.filter((rule) => rule.id !== ruleId));
|
||||
invalidateDataKeys('playgroundModels', 'modelCatalog');
|
||||
invalidateDataKeys('playgroundModels', 'apiKeyPolicyModels', 'modelCatalog');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('访问权限规则已删除。');
|
||||
} catch (err) {
|
||||
@@ -1007,7 +1014,7 @@ export function App() {
|
||||
try {
|
||||
const response = await batchAccessRules(token, input);
|
||||
setAccessRules(response.items);
|
||||
invalidateDataKeys('playgroundModels', 'modelCatalog');
|
||||
invalidateDataKeys('playgroundModels', 'apiKeyPolicyModels', 'modelCatalog');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('访问权限已更新。');
|
||||
} catch (err) {
|
||||
@@ -1355,7 +1362,7 @@ export function App() {
|
||||
apiKeyForm={apiKeyForm}
|
||||
apiKeySecret={apiKeySecret}
|
||||
apiKeySecretsById={apiKeySecretsById}
|
||||
apiKeyPolicyModels={playgroundModels}
|
||||
apiKeyPolicyModels={apiKeyPolicyModels}
|
||||
data={data}
|
||||
message={coreMessage}
|
||||
section={workspaceSection}
|
||||
@@ -1622,7 +1629,7 @@ function dataKeysForRoute(
|
||||
if (activePage === 'workspace') {
|
||||
if (workspaceSection === 'overview') return ['currentUser', 'currentUserGroups', 'apiKeys'];
|
||||
if (workspaceSection === 'billing') return ['wallet'];
|
||||
if (workspaceSection === 'apiKeys') return ['apiKeys', 'accessRules', 'playgroundModels'];
|
||||
if (workspaceSection === 'apiKeys') return ['apiKeys', 'accessRules', 'apiKeyPolicyModels'];
|
||||
if (workspaceSection === 'tasks') return ['tasks'];
|
||||
if (workspaceSection === 'transactions') return ['wallet', 'walletTransactions'];
|
||||
return [];
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getCurrentUser,
|
||||
getOpsManagementSkillMetadata,
|
||||
loginLocalAccount,
|
||||
listApiKeyAssignableModels,
|
||||
OIDC_BROWSER_SESSION_CREDENTIAL,
|
||||
startIdentityPairing,
|
||||
retireIdentityPairingSecurityEventConflict,
|
||||
@@ -231,6 +232,26 @@ describe('OIDC browser session transport', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('API Key permission resources', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('loads the user-owned resource pool independently from playable models', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ items: [] }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await listApiKeyAssignableModels('user-token');
|
||||
|
||||
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toContain('/api/v1/api-keys/assignable-models');
|
||||
expect(new Headers(init.headers).get('Authorization')).toBe('Bearer user-token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Public Agent resources', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
|
||||
@@ -459,6 +459,10 @@ export async function listApiKeyAccessRules(token: string): Promise<ListResponse
|
||||
return request<ListResponse<GatewayAccessRule>>('/api/v1/api-keys/access-rules', { token });
|
||||
}
|
||||
|
||||
export async function listApiKeyAssignableModels(token: string): Promise<ListResponse<PlatformModel>> {
|
||||
return request<ListResponse<PlatformModel>>('/api/v1/api-keys/assignable-models', { token });
|
||||
}
|
||||
|
||||
export async function createAccessRule(token: string, input: GatewayAccessRuleUpsertRequest): Promise<GatewayAccessRule> {
|
||||
return request<GatewayAccessRule>('/api/admin/access-rules', {
|
||||
body: input,
|
||||
|
||||
Reference in New Issue
Block a user