feat: ip access control

This commit is contained in:
jialin
2026-04-29 18:09:55 +08:00
committed by jialin
parent c68f546be9
commit ac99156c90
6 changed files with 85 additions and 32 deletions
+1 -1
View File
@@ -341,7 +341,7 @@ export default (props: any) => {
config={{ config={{
apiBaseUrl: GPUSTACK_API_BASE_URL, apiBaseUrl: GPUSTACK_API_BASE_URL,
theme: userSettings.theme, 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, isDarkTheme: userSettings.isDarkTheme,
defaultColorPrimary: COLOR_PRIMARY defaultColorPrimary: COLOR_PRIMARY
}} }}
+50 -21
View File
@@ -1,39 +1,66 @@
// columns.ts // columns.ts
import { tableSorter } from '@/config/settings'; 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 { useIntl } from '@umijs/max';
import { Tag } from 'antd'; import { MenuProps, Tag } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ListItem } from '../config/types'; import { ListItem } from '../config/types';
type APIKeyAction = Global.ActionItem<ListItem> & {
onClick?: (record: ListItem) => void;
};
interface ColumnsHookProps { interface ColumnsHookProps {
handleSelect: (val: string, record: ListItem) => void; handleSelect: (val: string, record: ListItem, item?: APIKeyAction) => void;
sortOrder: string[]; sortOrder: string[];
is_admin?: boolean; is_admin?: boolean;
onIPConfig?: (record: ListItem) => void;
} }
const actionList: Global.ActionItem<string>[] = [
{
label: 'common.button.edit',
key: 'edit',
icon: icons.EditOutlined
},
{
label: 'common.button.delete',
key: 'delete',
icon: icons.DeleteOutlined,
props: { danger: true }
}
];
const useModelsColumns = ({ const useModelsColumns = ({
handleSelect, handleSelect,
sortOrder, sortOrder,
is_admin is_admin,
onIPConfig
}: ColumnsHookProps): ColumnsType<ListItem> => { }: ColumnsHookProps): ColumnsType<ListItem> => {
const intl = useIntl(); const intl = useIntl();
const actionList = useMemo<APIKeyAction[]>(() => {
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: <IconFont type="icon-safe-ip" />,
onClick: (record: ListItem) => onIPConfig(record)
});
}
return list;
}, [onIPConfig]);
return useMemo(() => { return useMemo(() => {
return [ return [
{ {
@@ -172,13 +199,15 @@ const useModelsColumns = ({
span: 3, span: 3,
render: (text, record) => ( render: (text, record) => (
<DropdownButtons <DropdownButtons
items={actionList} items={actionList as MenuProps['items']}
onSelect={(val) => handleSelect(val, record)} onSelect={(val, item) =>
handleSelect(val, record, item as APIKeyAction)
}
/> />
) )
} }
]; ];
}, [intl, is_admin, handleSelect]); }, [intl, is_admin, handleSelect, actionList]);
}; };
export default useModelsColumns; export default useModelsColumns;
+32 -7
View File
@@ -3,6 +3,7 @@ import { PaginationKey } from '@/config/settings';
import type { PageActionType } from '@/config/types'; import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import useQueryUserList from '@/pages/users/services/use-query-user-list'; import useQueryUserList from '@/pages/users/services/use-query-user-list';
import { getGPUStackPlugin } from '@/plugins';
import { useModel } from '@@/plugin-model'; import { useModel } from '@@/plugin-model';
import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui'; import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -53,6 +54,12 @@ const APIKeys: React.FC = () => {
}); });
const intl = useIntl(); const intl = useIntl();
const apiKeyIPConfig = getGPUStackPlugin()?.APIKeyIPConfig;
const APIKeyIPConfigForm = apiKeyIPConfig?.form;
const { openIPConfigModalStatus, openIPConfigModal, closeIPConfigModal } =
apiKeyIPConfig?.useCreateIPConfig?.() || {};
const [openAddModal, setOpenAddModal] = useState<{ const [openAddModal, setOpenAddModal] = useState<{
open: boolean; open: boolean;
action: PageActionType; action: PageActionType;
@@ -115,13 +122,23 @@ const APIKeys: React.FC = () => {
}); });
}; };
const onSelect = useMemoizedFn(async (val: string, record: ListItem) => { const onSelect = useMemoizedFn(
if (val === 'delete') { async (
handleDelete(record); val: string,
} else if (val === 'edit') { record: ListItem,
handleEditKey(record); 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) => { const handleUserChange = (val: string) => {
handleQueryChange({ handleQueryChange({
@@ -152,7 +169,8 @@ const APIKeys: React.FC = () => {
const columns = useKeysColumns({ const columns = useKeysColumns({
handleSelect: onSelect, handleSelect: onSelect,
sortOrder, sortOrder,
is_admin: currentUser?.is_admin is_admin: currentUser?.is_admin,
onIPConfig: openIPConfigModal
}); });
return ( return (
@@ -207,6 +225,13 @@ const APIKeys: React.FC = () => {
onCancel={handleModalCancel} onCancel={handleModalCancel}
onOk={handleModalOk} onOk={handleModalOk}
></AddAPIKeyModal> ></AddAPIKeyModal>
{APIKeyIPConfigForm && (
<APIKeyIPConfigForm
open={openIPConfigModalStatus.open}
apiKey={openIPConfigModalStatus.currentData}
onClose={closeIPConfigModal}
/>
)}
<DeleteModal ref={modalRef}></DeleteModal> <DeleteModal ref={modalRef}></DeleteModal>
</> </>
); );
-1
View File
@@ -22,7 +22,6 @@ ${formatCurlArgs(parameters, isFormdata)}`.trim();
if (edit) { if (edit) {
curlCode = ` curlCode = `
curl ${host}${api} \\ curl ${host}${api} \\
-H "Content-Type: multipart/form-data" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''} -H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
-F image="@image.png" \\ -F image="@image.png" \\
-F mask="@mask.png" \\ -F mask="@mask.png" \\
+1 -1
View File
@@ -2,6 +2,7 @@ import { PageAction, PasswordReg } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import { import {
Input as CInput, Input as CInput,
FormDrawer,
IconFont, IconFont,
Select as SealSelect, Select as SealSelect,
Switch as SealSwitch Switch as SealSwitch
@@ -9,7 +10,6 @@ import {
import { useIntl, useModel } from '@umijs/max'; import { useIntl, useModel } from '@umijs/max';
import { Form, Select } from 'antd'; import { Form, Select } from 'antd';
import { useEffect } from 'react'; import { useEffect } from 'react';
import FormDrawer from '../../_components/form-drawer';
import { UserRoles, UserRolesOptions } from '../config'; import { UserRoles, UserRolesOptions } from '../config';
import { FormData, ListItem } from '../config/types'; import { FormData, ListItem } from '../config/types';
+1 -1
View File
@@ -124,7 +124,7 @@ export interface AppPlugin {
/** /**
* components extension * components extension
*/ */
components?: Record<string, ComponentType>; components?: Record<string, ComponentType<any>>;
/** /**
* Hooks extension * Hooks extension