chore: providers ux

This commit is contained in:
jialin
2026-02-03 18:21:08 +08:00
parent b2a8d27178
commit e8e982559d
32 changed files with 469 additions and 128 deletions
+3 -1
View File
@@ -1,6 +1,7 @@
import IconFont from '@/components/icon-font';
import {
ApiOutlined,
CopyOutlined,
DeleteOutlined,
DockerOutlined,
DownloadOutlined,
@@ -62,7 +63,8 @@ const icons = {
CaptivePortal: React.createElement(IconFont, { type: 'icon-captive_portal' }),
StarOutlined: React.createElement(StarOutlined),
Charger: React.createElement(IconFont, { type: 'icon-charger' }),
Disabled: React.createElement(IconFont, { type: 'icon-disabled' })
Disabled: React.createElement(IconFont, { type: 'icon-disabled' }),
CopyOutlined: React.createElement(CopyOutlined)
};
export default icons;
+3
View File
@@ -46,6 +46,9 @@ export default {
'noresult.provider.title': 'No Providers',
'noresult.provider.subTitle': 'No providers have been added yet.',
'noresult.provider.nofound': 'No matching providers found.',
'noresult.providers.title': 'No Providers',
'noresult.providers.subTitle': 'No providers have been added yet.',
'noresult.providers.nofound': 'No matching providers found.',
'noresult.accesses.title': 'No Accesses',
'noresult.accesses.subTitle': 'No accesses have been added yet.',
'noresult.accesses.nofound': 'No matching accesses found.'
+2 -1
View File
@@ -2,5 +2,6 @@ export default {
'providers.title': 'Providers',
'providers.button.add': 'Add Provider',
'providers.table.providerName': 'Provider',
'providers.table.modelsCounts': 'Models Count'
'providers.table.modelsCounts': 'Models Count',
'providers.table.proxy': 'Proxy'
};
+3
View File
@@ -46,6 +46,9 @@ export default {
'noresult.provider.title': 'No Providers',
'noresult.provider.subTitle': 'No providers have been added yet.',
'noresult.provider.nofound': 'No matching providers found.',
'noresult.providers.title': 'No Providers',
'noresult.providers.subTitle': 'No providers have been added yet.',
'noresult.providers.nofound': 'No matching providers found.',
'noresult.accesses.title': 'No Accesses',
'noresult.accesses.subTitle': 'No accesses have been added yet.',
'noresult.accesses.nofound': 'No matching accesses found.'
+2 -1
View File
@@ -2,5 +2,6 @@ export default {
'providers.title': 'Providers',
'providers.button.add': 'Add Provider',
'providers.table.providerName': 'Provider',
'providers.table.modelsCounts': 'Models Count'
'providers.table.modelsCounts': 'Models Count',
'providers.table.proxy': 'Proxy'
};
+6
View File
@@ -47,6 +47,9 @@ export default {
'noresult.provider.title': 'No Providers',
'noresult.provider.subTitle': 'No providers have been added yet.',
'noresult.provider.nofound': 'No matching providers found.',
'noresult.providers.title': 'No Providers',
'noresult.providers.subTitle': 'No providers have been added yet.',
'noresult.providers.nofound': 'No matching providers found.',
'noresult.accesses.title': 'No Accesses',
'noresult.accesses.subTitle': 'No accesses have been added yet.',
'noresult.accesses.nofound': 'No matching accesses found.'
@@ -63,6 +66,9 @@ export default {
// 5. 'noresult.provider.title': 'No Providers',
// 6. 'noresult.provider.subTitle': 'No providers have been added yet.',
// 7. 'noresult.provider.nofound': 'No matching providers found.',
// 5. 'noresult.providers.title': 'No Providers',
// 6. 'noresult.providers.subTitle': 'No providers have been added yet.',
// 7. 'noresult.providers.nofound': 'No matching providers found.',
// 8. 'noresult.accesses.title': 'No Accesses',
// 9. 'noresult.accesses.subTitle': 'No accesses have been added yet.',
// 10. 'noresult.accesses.nofound': 'No matching accesses found.'
+2 -1
View File
@@ -2,5 +2,6 @@ export default {
'providers.title': 'Providers',
'providers.button.add': 'Add Provider',
'providers.table.providerName': 'Provider',
'providers.table.modelsCounts': 'Models Count'
'providers.table.modelsCounts': 'Models Count',
'providers.table.proxy': 'Proxy'
};
+1 -2
View File
@@ -15,8 +15,7 @@ export default {
'menu.models.userModels': '我的模型',
'menu.models.benchmark': '基准测试',
'menu.models.benchmarkDetail': '基准测试详情',
'menu.models.provider': '渠道',
'menu.models.providers': '渠道',
'menu.models.providers': '提供商',
'menu.models.accesses': '接入',
'menu.modelCatalog': '模型库',
'menu.models.catalog': '模型库',
+3 -3
View File
@@ -41,9 +41,9 @@ export default {
'noresult.benchmark.title': '暂无基准测试',
'noresult.benchmark.subTitle': '尚未添加任何基准测试。',
'noresult.benchmark.nofound': '未找到匹配的基准测试',
'noresult.provider.title': '暂无渠道',
'noresult.provider.subTitle': '尚未添加任何渠道。',
'noresult.provider.nofound': '未找到匹配的渠道',
'noresult.providers.title': '暂无提供商',
'noresult.providers.subTitle': '尚未添加任何提供商。',
'noresult.providers.nofound': '未找到匹配的提供商',
'noresult.accesses.title': '暂无接入',
'noresult.accesses.subTitle': '尚未添加任何接入。',
'noresult.accesses.nofound': '未找到匹配的接入'
+5 -4
View File
@@ -1,6 +1,7 @@
export default {
'providers.title': '渠道',
'providers.button.add': '添加渠道',
'providers.table.providerName': '渠道',
'providers.table.modelsCounts': '模型数量'
'providers.title': '提供商',
'providers.button.add': '添加提供商',
'providers.table.providerName': '提供商',
'providers.table.modelsCounts': '模型数量',
'providers.table.proxy': '代理'
};
@@ -5,7 +5,7 @@ import {
WechatWorkOutlined
} from '@ant-design/icons';
import { Tag } from 'antd';
import { modelCategoriesMap } from '../config';
import { modelCategoriesMap } from '../llmodels/config';
export const categoryConfig = {
[modelCategoriesMap.reranker]: {
@@ -53,7 +53,7 @@ const ModelTag: React.FC<ModelTagProps> = ({ categoryKey, size }) => {
return (
<Tag
icon={config.icon}
variant="outlined"
variant={'outlined'}
style={{
height: size,
margin: 0,
@@ -7,10 +7,10 @@ import { Typography } from 'antd';
import classNames from 'classnames';
import _ from 'lodash';
import React, { useCallback, useMemo } from 'react';
import { categoryConfig } from '../../_components/model-tag';
import { modelCategories } from '../config';
import { CatalogItem as CatalogItemType } from '../config/types';
import '../style/catalog-item.less';
import { categoryConfig } from './model-tag';
interface CatalogItemProps {
activeId: number;
+1 -1
View File
@@ -8,6 +8,7 @@ import { Button } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { categoryConfig } from '../../_components/model-tag';
import {
modelCategories,
modelCategoriesMap,
@@ -17,7 +18,6 @@ import {
MyModelsStatusValueMap
} from '../config';
import { categoryToPathMap } from '../config/button-actions';
import { categoryConfig } from './model-tag';
const CardWrapper = styled.div`
&:hover {
@@ -9,7 +9,7 @@ import { Tooltip } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useMemo } from 'react';
import ModelTag from '../components/model-tag';
import ModelTag from '../../_components/model-tag';
import { ActionList, generateSource } from '../config/button-actions';
import { ListItem } from '../config/types';
@@ -1,4 +1,4 @@
import { categoryOptions, modelCategoriesMap } from '@/pages/llmodels/config';
import { modelCategoriesMap } from '@/pages/llmodels/config';
import {
CheckCircleOutlined,
CloseCircleOutlined,
@@ -6,6 +6,7 @@ import {
} from '@ant-design/icons';
import { Flex, Tag } from 'antd';
import React from 'react';
import { categoryConfig } from '../../_components/model-tag';
interface ProviderModelProps {
dataList: any[];
@@ -61,8 +62,10 @@ const ProviderModels: React.FC<ProviderModelProps> = () => {
: 'gray'
}
>
{model.name} (
{categoryOptions.find((item) => item.value === model.type)?.label})
{model.name}
<span style={{ marginLeft: 8 }}>
{categoryConfig[model.type]?.icon}
</span>
</Tag>
))}
</Flex>
@@ -0,0 +1,19 @@
import { PageActionType } from '@/config/types';
import { createContext, useContext } from 'react';
interface FormContextProps {
providerType?: string;
action?: PageActionType;
}
const FormContext = createContext<FormContextProps>({});
export const useFormContext = () => {
const context = useContext(FormContext);
if (!context) {
throw new Error('useFormContext must be used within a FormProvider');
}
return context;
};
export default FormContext;
+7 -2
View File
@@ -44,8 +44,8 @@ export const maasProviderOptions = Object.entries(maasProviderValueMap).map(
);
export const ProviderStatusValueMap: Record<string, string> = {
Ready: 'Ready',
InActive: 'Inactive'
Ready: 'ready',
InActive: 'inactive'
};
export const ProviderStatusLabelMap = {
@@ -65,6 +65,11 @@ export const rowActionList = [
label: 'common.button.edit',
icon: icons.EditOutlined
},
{
key: 'copy',
label: 'common.button.copy',
icon: icons.CopyOutlined
},
{
key: 'delete',
label: 'common.button.delete',
+9 -4
View File
@@ -9,23 +9,26 @@ export const mockDataList: MaasProviderItem[] = [
models: 5,
state_message: '',
provider: 'doubao',
proxy_config: {},
builtIn: false
},
{
id: 2,
models: 3,
name: 'Qwen',
state: 'Inactive',
state: 'inactive',
state_message: 'Provider is inactive',
provider: 'qwen',
proxy_config: false,
builtIn: true
},
{
id: 3,
models: 8,
name: 'OpenAI',
state: 'Ready',
state: 'ready',
state_message: '',
proxy_config: null,
provider: 'openai',
builtIn: false
},
@@ -33,18 +36,20 @@ export const mockDataList: MaasProviderItem[] = [
id: 4,
models: 2,
name: 'Deepseek',
state: 'Ready',
state: 'ready',
state_message: '',
provider: 'deepseek',
proxy_config: null,
builtIn: false
},
{
id: 5,
models: 4,
name: 'Anthropic',
state: 'Inactive',
state: 'inactive',
state_message: 'Provider is inactive',
provider: 'anthropic',
proxy_config: {},
builtIn: true
}
];
+1
View File
@@ -7,6 +7,7 @@ export interface MaasProviderItem {
state: string;
state_message: string;
provider: maasProviderType;
proxy_config?: any;
builtIn: boolean;
}
@@ -5,13 +5,14 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React, { forwardRef, useImperativeHandle } from 'react';
import { maasProviderType } from '../config';
import ProxyConfig from './proxy_config';
const AdvanceConfig: React.FC<{
action: PageActionType;
provider?: maasProviderType;
ref?: any;
}> = forwardRef(({ action, provider }, ref) => {
const [form] = Form.useForm();
const form = Form.useFormInstance();
const intl = useIntl();
const editorRef = React.useRef<any>(null);
const [fileContent, setFileContent] = React.useState<string>('');
@@ -28,7 +29,8 @@ const AdvanceConfig: React.FC<{
return (
<>
<div data-field="customConfig"></div>
<div data-field="advanceConfig"></div>
<ProxyConfig></ProxyConfig>
<Form.Item
hidden
name="custom_config"
@@ -43,14 +45,13 @@ const AdvanceConfig: React.FC<{
</Form.Item>
<YamlEditor
ref={editorRef}
title={'Yaml'}
title={'Custom Config (YAML)'}
value={fileContent}
height={300}
onUpload={(content) => {
setFileContent(content);
}}
></YamlEditor>
<div className="scroller-to-holder" style={{ height: 1 }}></div>
</>
);
});
+61 -38
View File
@@ -1,12 +1,20 @@
import IconFont from '@/components/icon-font';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import CollapsePanel from '@/pages/_components/collapse-panel';
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import { maasProviderType } from '../config';
import FormContext from '../config/form-context';
import { MaasProviderItem as ListItem } from '../config/types';
import AccessToken from './access-token';
import AdvanceConfig from './advance-config';
@@ -31,9 +39,9 @@ const TABKeysMap = {
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, provider, currentData, onFinish } = props;
const intl = useIntl();
const [form] = Form.useForm();
const { getScrollElementScrollableHeight } = useWrapperContext();
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]);
const [form] = Form.useForm();
const scrollTabsRef = useRef<any>(null);
const segmentOptions = [
@@ -50,17 +58,11 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
field: 'supportedModels'
},
{
value: TABKeysMap.CUSTOMCONFIG,
label: 'Custom Config',
value: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }),
icon: <IconFont type="icon-settings" />,
field: 'customConfig'
field: 'advanceConfig'
}
// {
// value: TABKeysMap.ADVANCED,
// label: intl.formatMessage({ id: 'resources.form.advanced' }),
// icon: <IconFont type="icon-settings" />,
// field: 'categories'
// }
];
const handleActiveChange = (key: string[]) => {
@@ -80,6 +82,15 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}
}));
useEffect(() => {
if (action === PageAction.EDIT && currentData) {
form.setFieldsValue({
...currentData,
models: currentData.models || []
});
}
}, [form, currentData, action]);
return (
<ScrollSpyTabs
ref={scrollTabsRef}
@@ -93,34 +104,46 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}}
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
>
<Form form={form}>
<Basic />
<AccessToken />
<CollapsePanel
activeKey={activeKey}
accordion={false}
onChange={handleOnCollapseChange}
items={[
{
key: TABKeysMap.SUPPORTEDMODELS,
label: 'Supported Models',
forceRender: true,
children: <SupportedModels></SupportedModels>
},
{
key: TABKeysMap.CUSTOMCONFIG,
label: 'Custom Config',
forceRender: true,
children: (
<AdvanceConfig
action={action}
provider={provider}
></AdvanceConfig>
)
<FormContext.Provider value={{ providerType: provider, action }}>
<Form
form={form}
onFinish={onFinish}
initialValues={{
proxy_config: {
enabled: false,
url: '',
timeout: 30
}
]}
></CollapsePanel>
</Form>
}}
>
<Basic />
<AccessToken />
<CollapsePanel
activeKey={activeKey}
accordion={false}
onChange={handleOnCollapseChange}
items={[
{
key: TABKeysMap.SUPPORTEDMODELS,
label: 'Supported Models',
forceRender: true,
children: <SupportedModels></SupportedModels>
},
{
key: TABKeysMap.ADVANCED,
label: 'Advanced',
forceRender: true,
children: (
<AdvanceConfig
action={action}
provider={provider}
></AdvanceConfig>
)
}
]}
></CollapsePanel>
</Form>
</FormContext.Provider>
</ScrollSpyTabs>
);
});
@@ -0,0 +1,59 @@
import CheckboxField from '@/components/seal-form/checkbox-field';
import SealInput from '@/components/seal-form/seal-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import { useFormContext } from '../config/form-context';
const AdvanceConfig = () => {
const form = Form.useFormInstance();
const intl = useIntl();
const { action } = useFormContext();
const proxyConfigRef = React.useRef<any>({});
const proxyConfigEnabled = Form.useWatch(['proxy_config', 'enabled'], form);
const handleSpeculativeEnabledChange = (e: any) => {
if (e.target.checked) {
form.setFieldValue('speculative_config', {
enabled: true,
url: proxyConfigRef.current?.url || '',
timeout: proxyConfigRef.current?.timeout || 30
});
} else {
proxyConfigRef.current = form.getFieldValue('proxy_config');
}
};
return (
<>
<Form.Item
name={['proxy_config', 'enabled']}
valuePropName="checked"
style={{ marginBottom: 8 }}
>
<CheckboxField
label={'Enable Proxy'}
onChange={handleSpeculativeEnabledChange}
></CheckboxField>
</Form.Item>
{proxyConfigEnabled && (
<>
<Form.Item name={['proxy_config', 'url']}>
<SealInput.Input
label={'Proxy URL'}
placeholder="http://proxy.example.com:8080"
></SealInput.Input>
</Form.Item>
<Form.Item name={['proxy_config', 'timeout']}>
<SealInput.Number
label={'Proxy Timeout'}
placeholder="30"
></SealInput.Number>
</Form.Item>
</>
)}
</>
);
};
export default AdvanceConfig;
@@ -22,6 +22,13 @@ const SupportedModels = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
const handleTestModel = (data: any) => {
data.loading = true;
setTimeout(() => {
data.loading = false;
}, 1000);
};
const renderModelItem = (
data: any,
{
@@ -32,6 +39,11 @@ const SupportedModels = () => {
return (
<SelectWrapper>
<SealSelect
suffixIcon={
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: 16 }}
/>
}
alwaysFocus={true}
value={data.value}
onChange={onChange}
@@ -42,12 +54,12 @@ const SupportedModels = () => {
{ label: 'Model C', value: 'model_c' }
]}
/>
<span className="icon-wrapper">
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: 16 }}
/>
</span>
<Button type="link" size="small">
<Button
type="link"
size="small"
loading={data.loading}
onClick={() => handleTestModel(data)}
>
Test
</Button>
</SelectWrapper>
@@ -19,11 +19,11 @@ const useCreateProvider = (options: { refresh: () => void }) => {
provider: null
});
const openModal = (action: PageActionType, row?: ListItem) => {
const openModal = (action: PageActionType, title: string, row?: ListItem) => {
setOpenModalStatus({
...openModalStatus,
open: true,
title: action === PageAction.CREATE ? 'Create Provider' : 'Edit Provider',
title: title,
action,
currentData: row,
provider: row ? row.provider : null
@@ -31,8 +31,13 @@ const useCreateProvider = (options: { refresh: () => void }) => {
};
const closeModal = () => {
setOpenModalStatus({ ...openModalStatus, open: false });
options.refresh();
setOpenModalStatus({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: null
});
};
return {
@@ -13,7 +13,6 @@ import {
maasProviderLabelMap,
providerIconsMap,
ProviderStatus,
ProviderStatusLabelMap,
rowActionList
} from '../config';
import { MaasProviderItem } from '../config/types';
@@ -69,15 +68,21 @@ const useProviderColumns = (
render: (value: number) => <span>{value}</span>
},
{
title: intl.formatMessage({ id: 'common.table.status' }),
dataIndex: 'state',
span: 4,
title: intl.formatMessage({ id: 'providers.table.proxy' }),
dataIndex: 'proxy_config',
span: 3,
render: (value: number) => <span>{value ? 'Configured' : 'None'}</span>
},
{
title: 'Token Settings',
dataIndex: 'tokens',
span: 3,
render: (value: string, record: MaasProviderItem) => (
<StatusTag
statusValue={{
status: ProviderStatus[value],
text: ProviderStatusLabelMap[value],
message: record.state_message || undefined
status: ProviderStatus['ready'],
text: 'Configured',
message: ''
}}
/>
)
@@ -86,7 +91,7 @@ const useProviderColumns = (
title: intl.formatMessage({ id: 'common.table.createTime' }),
dataIndex: 'created_at',
sorter: tableSorter(6),
span: 4,
span: 3,
render: (value: string) => (
<AutoTooltip ghost minWidth={20}>
{dayjs(value).format('YYYY-MM-DD HH:mm:ss')}
+21 -33
View File
@@ -62,17 +62,16 @@ const MaasProvider: React.FC = () => {
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
useExpandedRowKeys(expandAtom);
const intl = useIntl();
const {
openProviderModalStatus,
setOpenProviderModalStatus,
openProviderModal,
closeProviderModal
} = useCreateProvider({
refresh: handleSearch
});
const { openProviderModalStatus, openProviderModal, closeProviderModal } =
useCreateProvider({
refresh: handleSearch
});
const handleClickDropdown = () => {
openProviderModal('create');
openProviderModal(
'create',
intl.formatMessage({ id: 'providers.button.add' })
);
};
const handleModalOk = async (data: FormData) => {
@@ -87,39 +86,25 @@ const MaasProvider: React.FC = () => {
});
}
fetchData();
setOpenProviderModalStatus({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: null
});
closeProviderModal();
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {}
};
const handleModalCancel = () => {
console.log('handleModalCancel');
setOpenProviderModalStatus({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: null
});
closeProviderModal();
};
const handleEditProvider = (row: ListItem) => {
setOpenProviderModalStatus({
open: true,
action: PageAction.EDIT,
currentData: row,
title: intl.formatMessage(
openProviderModal(
PageAction.EDIT,
intl.formatMessage(
{ id: 'clusters.edit.cluster' },
{ cluster: row.name }
),
provider: row.provider
});
row
);
};
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
@@ -128,6 +113,9 @@ const MaasProvider: React.FC = () => {
} else if (val === 'delete') {
handleDelete({ ...row, name: row.name });
}
if (val === 'copy') {
openProviderModal(PageAction.EDIT, 'Copy Provider', row);
}
});
const handleOnToggleExpandAll = () => {
@@ -226,11 +214,11 @@ const MaasProvider: React.FC = () => {
image={<IconFont type="icon-extension-outline" />}
filters={_.omit(queryParams, ['sort_by'])}
noFoundText={intl.formatMessage({
id: 'noresult.provider.nofound'
id: 'noresult.providers.nofound'
})}
title={intl.formatMessage({ id: 'noresult.provider.title' })}
title={intl.formatMessage({ id: 'noresult.providers.title' })}
subTitle={intl.formatMessage({
id: 'noresult.provider.subTitle'
id: 'noresult.providers.subTitle'
})}
onClick={handleClickDropdown}
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
+31
View File
@@ -0,0 +1,31 @@
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import { categoryOptions } from '@/pages/llmodels/config';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { FormData } from '../config/types';
const Basic = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
return (
<>
<Form.Item name="name" data-field="name">
<SealInput.Input required label={'Model Name'} />
</Form.Item>
<Form.Item name="category">
<SealSelect options={categoryOptions} label="Category"></SealSelect>
</Form.Item>
<Form.Item name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
</>
);
};
export default Basic;
@@ -0,0 +1,38 @@
import SealInput from '@/components/seal-form/seal-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { FormData } from '../config/types';
const Endpoints = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
return (
<>
<Form.Item name="name" data-field="endpoints">
<SealInput.Input
required
label={intl.formatMessage({
id: 'common.table.name'
})}
/>
</Form.Item>
<Form.Item name="provider" hidden>
<SealInput.Input
label={intl.formatMessage({
id: 'providers.table.providerName'
})}
/>
</Form.Item>
<Form.Item name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
</>
);
};
export default Endpoints;
+90 -5
View File
@@ -1,17 +1,68 @@
import IconFont from '@/components/icon-font';
import { PageActionType } from '@/config/types';
import CollapsePanel from '@/pages/_components/collapse-panel';
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { forwardRef, useImperativeHandle } from 'react';
import { MaasProviderItem as ListItem } from '../config/types';
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import { maasProviderType } from '../config';
import { AccessItem as ListItem } from '../config/types';
import Basic from './basic';
import Endpoints from './endpoints';
import MetaData from './meta-data';
interface ProviderFormProps {
ref?: any;
action: PageActionType;
provider?: maasProviderType;
currentData?: ListItem; // Used when action is EDIT
onFinish: (values: FormData) => void;
}
const TABKeysMap = {
BASIC: 'basic',
METADATA: 'metadata',
ENDPOINTS: 'endpoints',
ADVANCED: 'advanced'
};
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, provider, currentData, onFinish } = props;
const intl = useIntl();
const { getScrollElementScrollableHeight } = useWrapperContext();
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]);
const [form] = Form.useForm();
const scrollTabsRef = useRef<any>(null);
const segmentOptions = [
{
value: TABKeysMap.BASIC,
label: 'Basic',
icon: <IconFont type="icon-basic" />,
field: 'name'
},
{
value: TABKeysMap.METADATA,
label: 'Metadata',
icon: <IconFont type="icon-speed" />,
field: 'metadata'
},
{
value: TABKeysMap.ENDPOINTS,
label: 'Endpoints',
icon: <IconFont type="icon-settings" />,
field: 'endpoints'
}
];
const handleActiveChange = (key: string[]) => {
setActiveKey(key);
};
const handleOnCollapseChange = (keys: string | string[]) => {
setActiveKey(Array.isArray(keys) ? keys : [keys]);
};
useImperativeHandle(ref, () => ({
submit: () => {
@@ -23,9 +74,43 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}));
return (
<Form form={form} layout="vertical">
{/* Form fields go here */}
</Form>
<ScrollSpyTabs
ref={scrollTabsRef}
defaultTarget="basic"
segmentOptions={segmentOptions}
activeKey={activeKey}
setActiveKey={handleActiveChange}
segmentedTop={{
top: 0,
offsetTop: 96
}}
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
>
<Form form={form}>
<Basic />
<CollapsePanel
activeKey={activeKey}
accordion={false}
onChange={handleOnCollapseChange}
items={[
{
key: TABKeysMap.METADATA,
label: 'Metadata',
forceRender: true,
children: <MetaData></MetaData>
},
{
key: TABKeysMap.ENDPOINTS,
label: 'Endpoints',
forceRender: true,
children: (
<Endpoints action={action} provider={provider}></Endpoints>
)
}
]}
></CollapsePanel>
</Form>
</ScrollSpyTabs>
);
});
@@ -0,0 +1,38 @@
import SealInput from '@/components/seal-form/seal-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { FormData } from '../config/types';
const MetaData = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
return (
<>
<Form.Item name="name" data-field="metadata">
<SealInput.Input
required
label={intl.formatMessage({
id: 'common.table.name'
})}
/>
</Form.Item>
<Form.Item name="provider" hidden>
<SealInput.Input
label={intl.formatMessage({
id: 'providers.table.providerName'
})}
/>
</Form.Item>
<Form.Item name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
</>
);
};
export default MetaData;
@@ -19,8 +19,14 @@ const useCreateAccess = (options: { refresh: () => void }) => {
provider: null
});
const openModal = () => {
setOpenModalStatus({ ...openModalStatus, open: true });
const openModal = (action: PageActionType, currentData?: ListItem) => {
setOpenModalStatus({
...openModalStatus,
open: true,
action,
currentData,
title: action === PageAction.CREATE ? 'Create Access' : 'Edit Access'
});
};
const closeModal = () => {
+1 -1
View File
@@ -67,7 +67,7 @@ const Accesses: React.FC = () => {
});
const handleClickDropdown = () => {
openAccessModal();
openAccessModal('create');
};
const handleModalOk = async (data: FormData) => {