fix: apikey model access
This commit is contained in:
@@ -170,6 +170,7 @@ const Extra = styled.div`
|
|||||||
|
|
||||||
const AddAfter = styled.div`
|
const AddAfter = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: 0 var(--ant-border-radius) var(--ant-border-radius) 0;
|
||||||
color: var(--ant-color-text-tertiary);
|
color: var(--ant-color-text-tertiary);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
padding-inline: calc(var(--ant-padding-sm) - 1px);
|
padding-inline: calc(var(--ant-padding-sm) - 1px);
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ interface SelectPanelProps {
|
|||||||
selectedKeys: string[];
|
selectedKeys: string[];
|
||||||
notFoundContent?: React.ReactNode;
|
notFoundContent?: React.ReactNode;
|
||||||
onSelectChange: (selectedKeys: string[]) => void;
|
onSelectChange: (selectedKeys: string[]) => void;
|
||||||
|
styles?: {
|
||||||
|
container?: React.CSSProperties;
|
||||||
|
left?: React.CSSProperties;
|
||||||
|
right?: React.CSSProperties;
|
||||||
|
header?: React.CSSProperties;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectPanel: React.FC<SelectPanelProps> = ({
|
const SelectPanel: React.FC<SelectPanelProps> = ({
|
||||||
@@ -46,6 +52,7 @@ const SelectPanel: React.FC<SelectPanelProps> = ({
|
|||||||
selectedKeys,
|
selectedKeys,
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
notFoundContent,
|
notFoundContent,
|
||||||
|
styles,
|
||||||
onSelectChange
|
onSelectChange
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -150,9 +157,13 @@ const SelectPanel: React.FC<SelectPanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelWrapper $maxHeight={height} $leftWidth={leftWidth}>
|
<PanelWrapper
|
||||||
|
$maxHeight={height}
|
||||||
|
$leftWidth={leftWidth}
|
||||||
|
style={styles?.container}
|
||||||
|
>
|
||||||
<Left>
|
<Left>
|
||||||
<Header>
|
<Header style={styles?.header}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import useAppUtils from '@/hooks/use-app-utils';
|
|||||||
import SelectPanel from '@/pages/_components/select-panel';
|
import SelectPanel from '@/pages/_components/select-panel';
|
||||||
import { queryMyModels } from '@/pages/llmodels/apis';
|
import { queryMyModels } from '@/pages/llmodels/apis';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Divider, Form, Radio } from 'antd';
|
import { Checkbox, Divider, Form, Radio } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ListItem } from '../../config/types';
|
import { accessScopeOptions } from '../../config';
|
||||||
|
import { FormData, ListItem } from '../../config/types';
|
||||||
|
|
||||||
const Label = styled.div`
|
const Label = styled.div`
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -29,6 +30,7 @@ const AllowModelsForm: React.FC<{
|
|||||||
form
|
form
|
||||||
) as string[];
|
) as string[];
|
||||||
const allowedType = Form.useWatch('allowed_type', form);
|
const allowedType = Form.useWatch('allowed_type', form);
|
||||||
|
const scope = Form.useWatch('scope', form) as string[];
|
||||||
const [modelList, setModelList] = useState<{ key: string; title: string }[]>(
|
const [modelList, setModelList] = useState<{ key: string; title: string }[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -56,11 +58,10 @@ const AllowModelsForm: React.FC<{
|
|||||||
const handleAllowTypeChange = (e: any) => {
|
const handleAllowTypeChange = (e: any) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
onValuesChange?.({ allowed_type: value }, form.getFieldsValue());
|
onValuesChange?.({ allowed_type: value }, form.getFieldsValue());
|
||||||
if (value === 'management') {
|
};
|
||||||
form.setFieldsValue({ scope: ['management'] });
|
|
||||||
} else {
|
const handleOnScopeChange = (checkedValues: any) => {
|
||||||
form.setFieldsValue({ scope: ['inference'] });
|
console.log('checkedValues', form.getFieldValue('allowed_type'));
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,61 +72,82 @@ const AllowModelsForm: React.FC<{
|
|||||||
<div>
|
<div>
|
||||||
<Divider></Divider>
|
<Divider></Divider>
|
||||||
<Label>{intl.formatMessage({ id: 'apikeys.access.permissions' })}</Label>
|
<Label>{intl.formatMessage({ id: 'apikeys.access.permissions' })}</Label>
|
||||||
<Form.Item
|
<Form.Item<FormData> name="scope" style={{ marginBottom: 8 }}>
|
||||||
name="allowed_type"
|
<Checkbox.Group
|
||||||
initialValue="all"
|
options={accessScopeOptions.map((item) => ({
|
||||||
style={{ marginBottom: 8 }}
|
label: intl.formatMessage({ id: item.label }),
|
||||||
>
|
value: item.value
|
||||||
<Radio.Group
|
}))}
|
||||||
onChange={handleAllowTypeChange}
|
onChange={handleOnScopeChange}
|
||||||
options={[
|
></Checkbox.Group>
|
||||||
{
|
|
||||||
label: intl.formatMessage({
|
|
||||||
id: 'apikeys.accessScope.management'
|
|
||||||
}),
|
|
||||||
value: 'management'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: intl.formatMessage({ id: 'apikeys.models.all' }),
|
|
||||||
value: 'all'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: intl.formatMessage({ id: 'apikeys.models.selected' }),
|
|
||||||
value: 'custom'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
></Radio.Group>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
{scope?.includes('inference') && (
|
||||||
name="allowed_model_names"
|
<div
|
||||||
hidden={allowedType === 'all' || allowedType === 'management'}
|
style={{
|
||||||
rules={[
|
padding: '12px',
|
||||||
{
|
backgroundColor: 'var(--ant-color-fill-quaternary)',
|
||||||
required: allowedType === 'custom',
|
borderRadius: 4
|
||||||
message: getRuleMessage(
|
|
||||||
'select',
|
|
||||||
intl.formatMessage({ id: 'models.table.models' })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SelectPanel
|
|
||||||
height={300}
|
|
||||||
searchPlaceholder={intl.formatMessage({ id: 'common.filter.name' })}
|
|
||||||
options={modelList}
|
|
||||||
selectedKeys={allowedModelNames || []}
|
|
||||||
notFoundContent={intl.formatMessage({
|
|
||||||
id: 'apikeys.models.noModelsFound'
|
|
||||||
})}
|
|
||||||
onSelectChange={(selectedKeys) => {
|
|
||||||
form.setFieldsValue({ allowed_model_names: selectedKeys });
|
|
||||||
onValuesChange?.(
|
|
||||||
{ allowed_model_names: selectedKeys },
|
|
||||||
form.getFieldsValue()
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</Form.Item>
|
<Form.Item name="allowed_type" noStyle>
|
||||||
|
<Radio.Group
|
||||||
|
onChange={handleAllowTypeChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'apikeys.models.all' }),
|
||||||
|
value: 'all'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'apikeys.models.selected' }),
|
||||||
|
value: 'custom'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
{allowedType === 'custom' && (
|
||||||
|
<Form.Item
|
||||||
|
name="allowed_model_names"
|
||||||
|
style={{ marginBottom: 0, marginTop: 12 }}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: allowedType === 'custom',
|
||||||
|
message: getRuleMessage(
|
||||||
|
'select',
|
||||||
|
intl.formatMessage({ id: 'models.table.models' })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SelectPanel
|
||||||
|
height={300}
|
||||||
|
styles={{
|
||||||
|
container: {
|
||||||
|
backgroundColor: 'var(--ant-color-bg-container) !important'
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
backgroundColor: 'var(--ant-color-bg-container) !important'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
searchPlaceholder={intl.formatMessage({
|
||||||
|
id: 'common.filter.name'
|
||||||
|
})}
|
||||||
|
options={modelList}
|
||||||
|
selectedKeys={allowedModelNames || []}
|
||||||
|
notFoundContent={intl.formatMessage({
|
||||||
|
id: 'apikeys.models.noModelsFound'
|
||||||
|
})}
|
||||||
|
onSelectChange={(selectedKeys) => {
|
||||||
|
form.setFieldsValue({ allowed_model_names: selectedKeys });
|
||||||
|
onValuesChange?.(
|
||||||
|
{ allowed_model_names: selectedKeys },
|
||||||
|
form.getFieldsValue()
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { PageActionType } from '@/config/types';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { accessScopeOptions, expirationOptions } from '../../config';
|
import { expirationOptions } from '../../config';
|
||||||
import { FormData, ListItem } from '../../config/types';
|
import { FormData, ListItem } from '../../config/types';
|
||||||
import AllowModelsForm from './allow-models';
|
import AllowModelsForm from './allow-models';
|
||||||
|
|
||||||
@@ -115,19 +115,7 @@ const APIKeyForm: React.FC<{
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Form.Item<FormData>
|
|
||||||
name="scope"
|
|
||||||
hidden
|
|
||||||
normalize={(value) => (value ? [value] : [])}
|
|
||||||
getValueProps={(value) => ({
|
|
||||||
value: Array.isArray(value) ? value[0] : value
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<SealSelect
|
|
||||||
options={accessScopeOptions}
|
|
||||||
label={intl.formatMessage({ id: 'models.table.accessScope' })}
|
|
||||||
></SealSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<AllowModelsForm
|
<AllowModelsForm
|
||||||
currentData={currentData}
|
currentData={currentData}
|
||||||
action={action}
|
action={action}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
..._.omit(formdata, ['allowed_type']),
|
..._.omit(formdata, ['allowed_type']),
|
||||||
allowed_model_names:
|
allowed_model_names:
|
||||||
formdata.allowed_type === 'all' ||
|
formdata.allowed_type === 'all' ||
|
||||||
formdata.allowed_type === 'management'
|
!formdata.scope?.includes('inference')
|
||||||
? []
|
? []
|
||||||
: formdata.allowed_model_names || []
|
: formdata.allowed_model_names || []
|
||||||
};
|
};
|
||||||
@@ -186,9 +186,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
scope: currentData.scope,
|
scope: currentData.scope,
|
||||||
allowed_type: currentData.allowed_model_names?.length
|
allowed_type: currentData.allowed_model_names?.length
|
||||||
? 'custom'
|
? 'custom'
|
||||||
: currentData.scope?.includes('management')
|
: 'all',
|
||||||
? 'management'
|
|
||||||
: 'all',
|
|
||||||
expires_in: parseExpireValue(currentData as ListItem),
|
expires_in: parseExpireValue(currentData as ListItem),
|
||||||
allowed_model_names: currentData.allowed_model_names || []
|
allowed_model_names: currentData.allowed_model_names || []
|
||||||
});
|
});
|
||||||
@@ -272,6 +270,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
onFinish={handleOnOk}
|
onFinish={handleOnOk}
|
||||||
preserve={false}
|
preserve={false}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
|
allowed_type: 'all',
|
||||||
scope: ['inference']
|
scope: ['inference']
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ export const expirationOptions = [
|
|||||||
|
|
||||||
export const accessScopeOptions = [
|
export const accessScopeOptions = [
|
||||||
{
|
{
|
||||||
label: 'management',
|
label: 'apikeys.accessScope.management',
|
||||||
value: 'management',
|
value: 'management',
|
||||||
description: 'v2/',
|
description: 'v2/',
|
||||||
locale: true
|
locale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'inference',
|
label: 'apikeys.table.bindModels',
|
||||||
value: 'inference',
|
value: 'inference',
|
||||||
description: 'v1/',
|
description: 'v1/',
|
||||||
locale: true
|
locale: true
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { 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 { accessScopeOptions } from '../config';
|
|
||||||
import { ListItem } from '../config/types';
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
interface ColumnsHookProps {
|
interface ColumnsHookProps {
|
||||||
@@ -16,7 +15,7 @@ interface ColumnsHookProps {
|
|||||||
sortOrder: string[];
|
sortOrder: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionList: Global.ActionItem[] = [
|
const actionList: Global.ActionItem<string>[] = [
|
||||||
{
|
{
|
||||||
label: 'common.button.edit',
|
label: 'common.button.edit',
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
@@ -36,20 +35,6 @@ const useModelsColumns = ({
|
|||||||
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const renderAPIs = (record: ListItem) => {
|
|
||||||
const option = accessScopeOptions.find(
|
|
||||||
(item) => item.value === record.scope?.[0]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<span className="flex-center gap-4">
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: option?.label || ''
|
|
||||||
})}
|
|
||||||
{option?.description && <span>[{option?.description}]</span>}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -112,13 +97,34 @@ const useModelsColumns = ({
|
|||||||
showTitle: false
|
showTitle: false
|
||||||
},
|
},
|
||||||
render: (text: string[], record: ListItem) => (
|
render: (text: string[], record: ListItem) => (
|
||||||
<AutoTooltip ghost>
|
<div className="flex-column gap-4">
|
||||||
{record.scope?.[0] === 'management'
|
{record.scope?.includes('management') && (
|
||||||
? intl.formatMessage({ id: 'apikeys.accessScope.management' })
|
<AutoTooltip ghost>
|
||||||
: record.allowed_model_names?.length
|
{intl.formatMessage({ id: 'apikeys.accessScope.management' })}
|
||||||
? record.allowed_model_names.join(', ')
|
</AutoTooltip>
|
||||||
: intl.formatMessage({ id: 'apikeys.models.all' })}
|
)}
|
||||||
</AutoTooltip>
|
{record.scope?.includes('inference') && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--ant-color-split)',
|
||||||
|
color: 'var(--ant-color-text-tertiary)',
|
||||||
|
backgroundColor: 'var(--ant-color-fill-quaternary)',
|
||||||
|
borderRadius: 4,
|
||||||
|
paddingInline: 8,
|
||||||
|
flexGrow: 0,
|
||||||
|
maxWidth: '100%',
|
||||||
|
width: 'max-content',
|
||||||
|
display: 'flex'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{record.allowed_model_names?.length
|
||||||
|
? record.allowed_model_names.join(', ')
|
||||||
|
: intl.formatMessage({ id: 'apikeys.models.all' })}
|
||||||
|
</AutoTooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user