From ac99156c90ce4d72fdf754e94cd41981d01870ff Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 29 Apr 2026 15:55:31 +0800 Subject: [PATCH] feat: ip access control --- src/layouts/index.tsx | 2 +- src/pages/api-keys/hooks/use-keys-columns.tsx | 71 +++++++++++++------ src/pages/api-keys/index.tsx | 39 ++++++++-- src/pages/playground/view-code/image.ts | 1 - src/pages/users/components/add-modal.tsx | 2 +- src/plugins/types.ts | 2 +- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 45fee43b..d8ca78ad 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -341,7 +341,7 @@ export default (props: any) => { config={{ apiBaseUrl: GPUSTACK_API_BASE_URL, theme: userSettings.theme, - iconUrl: '//at.alicdn.com/t/c/font_4613488_mk9rqojjoqk.js', + iconUrl: '//at.alicdn.com/t/c/font_4613488_ueevrwt2v9.js', isDarkTheme: userSettings.isDarkTheme, defaultColorPrimary: COLOR_PRIMARY }} diff --git a/src/pages/api-keys/hooks/use-keys-columns.tsx b/src/pages/api-keys/hooks/use-keys-columns.tsx index 0a441465..5fcf0612 100644 --- a/src/pages/api-keys/hooks/use-keys-columns.tsx +++ b/src/pages/api-keys/hooks/use-keys-columns.tsx @@ -1,39 +1,66 @@ // columns.ts import { tableSorter } from '@/config/settings'; -import { AutoTooltip, DropdownButtons, icons } from '@gpustack/core-ui'; +import { getGPUStackPlugin } from '@/plugins'; +import { + AutoTooltip, + DropdownButtons, + IconFont, + icons +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; -import { Tag } from 'antd'; +import { MenuProps, Tag } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; import { ListItem } from '../config/types'; + +type APIKeyAction = Global.ActionItem & { + onClick?: (record: ListItem) => void; +}; + interface ColumnsHookProps { - handleSelect: (val: string, record: ListItem) => void; + handleSelect: (val: string, record: ListItem, item?: APIKeyAction) => void; sortOrder: string[]; is_admin?: boolean; + onIPConfig?: (record: ListItem) => void; } -const actionList: Global.ActionItem[] = [ - { - label: 'common.button.edit', - key: 'edit', - icon: icons.EditOutlined - }, - { - label: 'common.button.delete', - key: 'delete', - icon: icons.DeleteOutlined, - props: { danger: true } - } -]; - const useModelsColumns = ({ handleSelect, sortOrder, - is_admin + is_admin, + onIPConfig }: ColumnsHookProps): ColumnsType => { const intl = useIntl(); + const actionList = useMemo(() => { + const list: APIKeyAction[] = [ + { + label: 'common.button.edit', + key: 'edit', + icon: icons.EditOutlined + }, + { + label: 'common.button.delete', + key: 'delete', + icon: icons.DeleteOutlined, + props: { danger: true } + } + ]; + + const ipConfigComponent = getGPUStackPlugin()?.APIKeyIPConfig?.form; + if (ipConfigComponent && onIPConfig) { + list.splice(1, 0, { + label: 'apikeys.button.ipConfig', + key: 'ipConfig', + icon: , + onClick: (record: ListItem) => onIPConfig(record) + }); + } + + return list; + }, [onIPConfig]); + return useMemo(() => { return [ { @@ -172,13 +199,15 @@ const useModelsColumns = ({ span: 3, render: (text, record) => ( handleSelect(val, record)} + items={actionList as MenuProps['items']} + onSelect={(val, item) => + handleSelect(val, record, item as APIKeyAction) + } /> ) } ]; - }, [intl, is_admin, handleSelect]); + }, [intl, is_admin, handleSelect, actionList]); }; export default useModelsColumns; diff --git a/src/pages/api-keys/index.tsx b/src/pages/api-keys/index.tsx index d3369b6a..d738b77f 100644 --- a/src/pages/api-keys/index.tsx +++ b/src/pages/api-keys/index.tsx @@ -3,6 +3,7 @@ import { PaginationKey } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; import useQueryUserList from '@/pages/users/services/use-query-user-list'; +import { getGPUStackPlugin } from '@/plugins'; import { useModel } from '@@/plugin-model'; import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; @@ -53,6 +54,12 @@ const APIKeys: React.FC = () => { }); const intl = useIntl(); + + const apiKeyIPConfig = getGPUStackPlugin()?.APIKeyIPConfig; + const APIKeyIPConfigForm = apiKeyIPConfig?.form; + const { openIPConfigModalStatus, openIPConfigModal, closeIPConfigModal } = + apiKeyIPConfig?.useCreateIPConfig?.() || {}; + const [openAddModal, setOpenAddModal] = useState<{ open: boolean; action: PageActionType; @@ -115,13 +122,23 @@ const APIKeys: React.FC = () => { }); }; - const onSelect = useMemoizedFn(async (val: string, record: ListItem) => { - if (val === 'delete') { - handleDelete(record); - } else if (val === 'edit') { - handleEditKey(record); + const onSelect = useMemoizedFn( + async ( + val: string, + record: ListItem, + item?: { onClick?: (r: ListItem) => void } + ) => { + if (item?.onClick) { + item.onClick(record); + return; + } + if (val === 'delete') { + handleDelete(record); + } else if (val === 'edit') { + handleEditKey(record); + } } - }); + ); const handleUserChange = (val: string) => { handleQueryChange({ @@ -152,7 +169,8 @@ const APIKeys: React.FC = () => { const columns = useKeysColumns({ handleSelect: onSelect, sortOrder, - is_admin: currentUser?.is_admin + is_admin: currentUser?.is_admin, + onIPConfig: openIPConfigModal }); return ( @@ -207,6 +225,13 @@ const APIKeys: React.FC = () => { onCancel={handleModalCancel} onOk={handleModalOk} > + {APIKeyIPConfigForm && ( + + )} ); diff --git a/src/pages/playground/view-code/image.ts b/src/pages/playground/view-code/image.ts index 0eb08b7d..a40ecb91 100644 --- a/src/pages/playground/view-code/image.ts +++ b/src/pages/playground/view-code/image.ts @@ -22,7 +22,6 @@ ${formatCurlArgs(parameters, isFormdata)}`.trim(); if (edit) { curlCode = ` curl ${host}${api} \\ --H "Content-Type: multipart/form-data" \\ -H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''} -F image="@image.png" \\ -F mask="@mask.png" \\ diff --git a/src/pages/users/components/add-modal.tsx b/src/pages/users/components/add-modal.tsx index cea8d8aa..4683f8e0 100644 --- a/src/pages/users/components/add-modal.tsx +++ b/src/pages/users/components/add-modal.tsx @@ -2,6 +2,7 @@ import { PageAction, PasswordReg } from '@/config'; import { PageActionType } from '@/config/types'; import { Input as CInput, + FormDrawer, IconFont, Select as SealSelect, Switch as SealSwitch @@ -9,7 +10,6 @@ import { import { useIntl, useModel } from '@umijs/max'; import { Form, Select } from 'antd'; import { useEffect } from 'react'; -import FormDrawer from '../../_components/form-drawer'; import { UserRoles, UserRolesOptions } from '../config'; import { FormData, ListItem } from '../config/types'; diff --git a/src/plugins/types.ts b/src/plugins/types.ts index 1458aa76..3d3fea01 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -124,7 +124,7 @@ export interface AppPlugin { /** * components extension */ - components?: Record; + components?: Record>; /** * Hooks extension