feat: ip access control
This commit is contained in:
@@ -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
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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" \\
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export interface AppPlugin {
|
|||||||
/**
|
/**
|
||||||
* components extension
|
* components extension
|
||||||
*/
|
*/
|
||||||
components?: Record<string, ComponentType>;
|
components?: Record<string, ComponentType<any>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks extension
|
* Hooks extension
|
||||||
|
|||||||
Reference in New Issue
Block a user