feat: refine api key permissions and admin routes
This commit is contained in:
+66
-4
@@ -22,6 +22,7 @@ import type {
|
||||
} from '@easyai-ai-gateway/contracts';
|
||||
import {
|
||||
batchAccessRules,
|
||||
batchApiKeyAccessRules,
|
||||
createAccessRule,
|
||||
createApiKey,
|
||||
createGatewayUser,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
createTenant,
|
||||
createUserGroup,
|
||||
deleteAccessRule,
|
||||
deleteApiKey,
|
||||
deleteGatewayUser,
|
||||
deletePlatform,
|
||||
deleteTenant,
|
||||
@@ -36,6 +38,7 @@ import {
|
||||
getHealth,
|
||||
getTask,
|
||||
listAccessRules,
|
||||
listApiKeyAccessRules,
|
||||
listApiKeys,
|
||||
listBaseModels,
|
||||
listCatalogProviders,
|
||||
@@ -148,7 +151,7 @@ export function App() {
|
||||
const [users, setUsers] = useState<GatewayUser[]>([]);
|
||||
const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
|
||||
const [apiKeys, setApiKeys] = useState<GatewayApiKey[]>([]);
|
||||
const [apiKeyForm, setApiKeyForm] = useState<ApiKeyForm>({ name: 'Local smoke key' });
|
||||
const [apiKeyForm, setApiKeyForm] = useState<ApiKeyForm>({ name: 'Local smoke key', expiresAt: '' });
|
||||
const [apiKeySecret, setApiKeySecret] = useState('');
|
||||
const [apiKeySecretsById, setApiKeySecretsById] = useState<Record<string, string>>({});
|
||||
const [selectedPlaygroundApiKeyId, setSelectedPlaygroundApiKeyId] = useState('');
|
||||
@@ -234,6 +237,10 @@ export function App() {
|
||||
await ensureRouteData(nextToken, true);
|
||||
}
|
||||
|
||||
function invalidateDataKeys(...keys: DataKey[]) {
|
||||
keys.forEach((key) => loadedDataKeysRef.current.delete(key));
|
||||
}
|
||||
|
||||
async function ensureRouteData(nextToken = token, force = false) {
|
||||
await ensureData(dataKeysForRoute(activePage, adminSection, workspaceSection, Boolean(nextToken)), nextToken, force);
|
||||
}
|
||||
@@ -321,7 +328,9 @@ export function App() {
|
||||
setUserGroups((await listUserGroups(nextToken)).items);
|
||||
return;
|
||||
case 'accessRules':
|
||||
setAccessRules((await listAccessRules(nextToken)).items);
|
||||
setAccessRules((await (activePage === 'workspace' && workspaceSection === 'apiKeys'
|
||||
? listApiKeyAccessRules(nextToken)
|
||||
: listAccessRules(nextToken))).items);
|
||||
return;
|
||||
case 'apiKeys':
|
||||
setApiKeys((await listApiKeys(nextToken)).items);
|
||||
@@ -369,16 +378,22 @@ export function App() {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
try {
|
||||
const response = await createApiKey(token, { name: apiKeyForm.name, scopes: ['chat', 'image', 'video'] });
|
||||
const response = await createApiKey(token, {
|
||||
name: apiKeyForm.name,
|
||||
scopes: ['chat', 'image', 'video'],
|
||||
expiresAt: apiKeyForm.expiresAt ? new Date(apiKeyForm.expiresAt).toISOString() : undefined,
|
||||
});
|
||||
setApiKeySecret(response.secret);
|
||||
setApiKeySecretsById((current) => ({ ...current, [response.apiKey.id]: response.secret }));
|
||||
setSelectedPlaygroundApiKeyId(response.apiKey.id);
|
||||
setApiKeys((current) => [response.apiKey, ...current.filter((item) => item.id !== response.apiKey.id)]);
|
||||
setApiKeyForm({ name: '', expiresAt: '' });
|
||||
setCoreState('ready');
|
||||
setCoreMessage('API Key 已创建,secret 仅展示一次。');
|
||||
} catch (err) {
|
||||
setCoreState('error');
|
||||
setCoreMessage(err instanceof Error ? err.message : '创建 API Key 失败');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,6 +476,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');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(userId ? '用户已更新。' : '用户已创建。');
|
||||
} catch (err) {
|
||||
@@ -508,6 +524,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('playgroundModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('用户组已删除。');
|
||||
} catch (err) {
|
||||
@@ -517,12 +534,35 @@ export function App() {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeAPIKey(apiKeyId: string) {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
try {
|
||||
await deleteApiKey(token, apiKeyId);
|
||||
setApiKeys((current) => current.filter((item) => item.id !== apiKeyId));
|
||||
setAccessRules((current) => current.filter((rule) => !(rule.subjectType === 'api_key' && rule.subjectId === apiKeyId)));
|
||||
setApiKeySecretsById((current) => {
|
||||
const next = { ...current };
|
||||
delete next[apiKeyId];
|
||||
return next;
|
||||
});
|
||||
if (selectedPlaygroundApiKeyId === apiKeyId) setSelectedPlaygroundApiKeyId('');
|
||||
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('');
|
||||
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');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(ruleId ? '访问权限规则已更新。' : '访问权限规则已创建。');
|
||||
} catch (err) {
|
||||
@@ -538,6 +578,7 @@ export function App() {
|
||||
try {
|
||||
await deleteAccessRule(token, ruleId);
|
||||
setAccessRules((current) => current.filter((rule) => rule.id !== ruleId));
|
||||
invalidateDataKeys('playgroundModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('访问权限规则已删除。');
|
||||
} catch (err) {
|
||||
@@ -553,6 +594,7 @@ export function App() {
|
||||
try {
|
||||
const response = await batchAccessRules(token, input);
|
||||
setAccessRules(response.items);
|
||||
invalidateDataKeys('playgroundModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('访问权限已更新。');
|
||||
} catch (err) {
|
||||
@@ -562,6 +604,21 @@ export function App() {
|
||||
}
|
||||
}
|
||||
|
||||
async function batchSaveAPIKeyAccessRules(input: GatewayAccessRuleBatchRequest) {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
try {
|
||||
const response = await batchApiKeyAccessRules(token, input);
|
||||
setAccessRules(response.items);
|
||||
setCoreState('ready');
|
||||
setCoreMessage('API Key 权限已更新。');
|
||||
} catch (err) {
|
||||
setCoreState('error');
|
||||
setCoreMessage(err instanceof Error ? err.message : '批量更新 API Key 权限失败');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function submitTask(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
const credential = apiKeySecret || token;
|
||||
@@ -695,9 +752,14 @@ export function App() {
|
||||
<WorkspacePage
|
||||
apiKeyForm={apiKeyForm}
|
||||
apiKeySecret={apiKeySecret}
|
||||
apiKeySecretsById={apiKeySecretsById}
|
||||
apiKeyPolicyModels={playgroundModels}
|
||||
data={data}
|
||||
message={coreMessage}
|
||||
section={workspaceSection}
|
||||
state={coreState}
|
||||
onBatchAccessRules={batchSaveAPIKeyAccessRules}
|
||||
onDeleteApiKey={removeAPIKey}
|
||||
onApiKeyFormChange={setApiKeyForm}
|
||||
onSectionChange={navigateWorkspaceSection}
|
||||
onSubmitApiKey={submitAPIKey}
|
||||
@@ -861,7 +923,7 @@ function dataKeysForRoute(
|
||||
|
||||
if (activePage === 'workspace') {
|
||||
if (workspaceSection === 'overview') return ['users', 'userGroups', 'apiKeys'];
|
||||
if (workspaceSection === 'apiKeys') return ['apiKeys'];
|
||||
if (workspaceSection === 'apiKeys') return ['apiKeys', 'accessRules', 'playgroundModels'];
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user