fix: failed field auto scroll in view

This commit is contained in:
jialin
2025-11-07 16:54:33 +08:00
parent fdb6ac7575
commit 9ffd4def64
10 changed files with 73 additions and 351 deletions
@@ -35,6 +35,7 @@ const NotFoundContent: React.FC<{
<NoContent> <NoContent>
{notFoundContent || ( {notFoundContent || (
<Empty <Empty
style={{ marginBlock: 0 }}
description={intl.formatMessage({ id: 'common.data.empty' })} description={intl.formatMessage({ id: 'common.data.empty' })}
image={Empty.PRESENTED_IMAGE_SIMPLE} image={Empty.PRESENTED_IMAGE_SIMPLE}
/> />
+3 -3
View File
@@ -22,9 +22,9 @@ export default {
'noresult.cluster.title': '暂无集群', 'noresult.cluster.title': '暂无集群',
'noresult.cluster.subTitle': '尚未添加任何集群。', 'noresult.cluster.subTitle': '尚未添加任何集群。',
'noresult.cluster.nofound': '未找到匹配的集群', 'noresult.cluster.nofound': '未找到匹配的集群',
'noresult.credentials.title': '暂无凭', 'noresult.credentials.title': '暂无凭',
'noresult.credentials.subTitle': '尚未添加任何凭。', 'noresult.credentials.subTitle': '尚未添加任何凭。',
'noresult.credentials.nofound': '未找到匹配的凭', 'noresult.credentials.nofound': '未找到匹配的凭',
'noresult.users.title': '暂无用户', 'noresult.users.title': '暂无用户',
'noresult.users.subTitle': '尚未添加任何用户。', 'noresult.users.subTitle': '尚未添加任何用户。',
'noresult.users.nofound': '未找到匹配的用户', 'noresult.users.nofound': '未找到匹配的用户',
+1 -1
View File
@@ -608,7 +608,7 @@ const Models: React.FC<ModelsProps> = ({
></SearchOutlined> ></SearchOutlined>
} }
placeholder={intl.formatMessage({ id: 'common.filter.name' })} placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 160 }} style={{ width: 200 }}
size="large" size="large"
allowClear allowClear
onChange={handleNameChange} onChange={handleNameChange}
+44 -16
View File
@@ -30,6 +30,8 @@ import BasicForm from './basic';
import Performance from './performance'; import Performance from './performance';
import ScheduleTypeForm from './schedule-type'; import ScheduleTypeForm from './schedule-type';
const baseRequiredFields = ['name', 'source'];
const advancedRequiredFields = ['backend', 'image_name', 'run_command']; const advancedRequiredFields = ['backend', 'image_name', 'run_command'];
const scheduleRequiredFields = ['gpu_selector']; const scheduleRequiredFields = ['gpu_selector'];
@@ -96,24 +98,31 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
const scheduleType = Form.useWatch('scheduleType', form); const scheduleType = Form.useWatch('scheduleType', form);
const [target, setTarget] = React.useState<string>('basic'); const [target, setTarget] = React.useState<string>('basic');
const TABKeysMap = {
BASIC: 'basic',
SCHEDULING: 'scheduling',
PERFORMANCE: 'performance',
ADVANCED: 'advanced'
};
const segmentOptions = [ const segmentOptions = [
{ {
value: 'basic', value: TABKeysMap.BASIC,
label: intl.formatMessage({ id: 'common.title.basicInfo' }), label: intl.formatMessage({ id: 'common.title.basicInfo' }),
field: 'name' field: 'name'
}, },
{ {
value: 'scheduling', value: TABKeysMap.SCHEDULING,
label: intl.formatMessage({ id: 'models.form.scheduling' }), label: intl.formatMessage({ id: 'models.form.scheduling' }),
field: 'scheduleType' field: 'scheduleType'
}, },
{ {
value: 'performance', value: TABKeysMap.PERFORMANCE,
label: intl.formatMessage({ id: 'models.form.performance' }), label: intl.formatMessage({ id: 'models.form.performance' }),
field: 'extended_kv_cache.enabled' field: 'extended_kv_cache.enabled'
}, },
{ {
value: 'advanced', value: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }), label: intl.formatMessage({ id: 'resources.form.advanced' }),
field: 'categories' field: 'categories'
} }
@@ -266,6 +275,12 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
setActiveKey(Array.isArray(keys) ? keys : [keys]); setActiveKey(Array.isArray(keys) ? keys : [keys]);
}; };
const handleTargetChange = async (val: any) => {
setTarget(val);
await scrollToSegment(val, { offsetTop: SegmentedTop.offsetTop });
};
const handleOnFinishFailed = (errorInfo: any) => { const handleOnFinishFailed = (errorInfo: any) => {
const { errorFields } = errorInfo; const { errorFields } = errorInfo;
if (errorFields && errorFields.length > 0) { if (errorFields && errorFields.length > 0) {
@@ -283,16 +298,35 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
scheduleRequiredFields.includes(name) scheduleRequiredFields.includes(name)
); );
const isBaseRequired = names.some((name: string) =>
baseRequiredFields.includes(name)
);
if (isScheduleRequired) { if (isScheduleRequired) {
collapseKeys.push('scheduling'); collapseKeys.push(TABKeysMap.SCHEDULING);
} }
if (isPerformanceRequired) { if (isPerformanceRequired) {
collapseKeys.push('performance'); collapseKeys.push(TABKeysMap.PERFORMANCE);
} }
if (isAdvancedRequired) { if (isAdvancedRequired) {
collapseKeys.push('advanced'); collapseKeys.push(TABKeysMap.ADVANCED);
}
if (isBaseRequired) {
handleTargetChange(TABKeysMap.BASIC);
} else if (isScheduleRequired) {
handleTargetChange(TABKeysMap.SCHEDULING);
} else if (isPerformanceRequired) {
handleTargetChange(TABKeysMap.PERFORMANCE);
} else if (isAdvancedRequired && formKey === DeployFormKeyMap.CATALOG) {
handleTargetChange(TABKeysMap.ADVANCED);
} else if (
isAdvancedRequired &&
formKey === DeployFormKeyMap.DEPLOYMENT
) {
handleTargetChange(TABKeysMap.BASIC);
} }
setActiveKey((prev: string[]) => [ setActiveKey((prev: string[]) => [
@@ -301,12 +335,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
} }
}; };
const handleTargetChange = async (val: any) => {
setTarget(val);
await scrollToSegment(val, { offsetTop: SegmentedTop.offsetTop });
};
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return { return {
form: form, form: form,
@@ -408,19 +436,19 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
onChange={handleOnCollapseChange} onChange={handleOnCollapseChange}
items={[ items={[
{ {
key: 'scheduling', key: TABKeysMap.SCHEDULING,
label: intl.formatMessage({ id: 'models.form.scheduling' }), label: intl.formatMessage({ id: 'models.form.scheduling' }),
forceRender: true, forceRender: true,
children: <ScheduleTypeForm></ScheduleTypeForm> children: <ScheduleTypeForm></ScheduleTypeForm>
}, },
{ {
key: 'performance', key: TABKeysMap.PERFORMANCE,
label: intl.formatMessage({ id: 'models.form.performance' }), label: intl.formatMessage({ id: 'models.form.performance' }),
forceRender: true, forceRender: true,
children: <Performance></Performance> children: <Performance></Performance>
}, },
{ {
key: 'advanced', key: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }), label: intl.formatMessage({ id: 'resources.form.advanced' }),
forceRender: true, forceRender: true,
children: <AdvanceConfig></AdvanceConfig> children: <AdvanceConfig></AdvanceConfig>
+2 -11
View File
@@ -172,19 +172,10 @@ const UserModels: React.FC = () => {
}, [dataSource.dataList, status]); }, [dataSource.dataList, status]);
return ( return (
<PageBox <PageBox>
ghost
header={{
title: intl.formatMessage({ id: 'menu.models.userModels' }),
style: {
paddingInline: 'var(--layout-content-header-inlinepadding)'
},
breadcrumb: {}
}}
extra={[]}
>
<PageTools <PageTools
marginBottom={22} marginBottom={22}
marginTop={0}
left={ left={
<Space> <Space>
<Input <Input
@@ -1,110 +0,0 @@
import { useIntl } from '@umijs/max';
import { Space, Typography } from 'antd';
import React, { useMemo } from 'react';
const { Text } = Typography;
interface AddWorkerMacOSProps {
token?: string;
platform: {
os: string;
downloadurl: string;
supportVersions: string;
};
}
const registerWorkerSteps = [
'resources.register.worker.step2',
`resources.register.worker.step4`,
'resources.register.worker.step5',
'resources.register.worker.step6',
`resources.register.worker.step7`
];
const AddWorkerMacOS: React.FC<AddWorkerMacOSProps> = ({ platform }) => {
const intl = useIntl();
const labels = useMemo(
() => ({
step1: intl.formatMessage({ id: 'resources.worker.add.step1' }),
step2: intl.formatMessage({ id: 'resources.worker.add.step2' }),
step2Tips: intl.formatMessage({ id: 'resources.worker.add.step2.tips' }),
step3: intl.formatMessage({ id: 'resources.worker.add.step3' }),
linuxOrMac: intl.formatMessage({ id: 'resources.worker.linuxormaxos' })
}),
[intl]
);
return (
<div className="script-install">
<h3 className="font-size-14 font-600">
1.{' '}
{intl.formatMessage(
{ id: 'resources.register.install.title' },
{ os: platform.os }
)}
</h3>
<Typography.Paragraph type="secondary" style={{ paddingLeft: 14 }}>
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage(
{ id: 'resources.register.download' },
{
versions: intl.formatMessage({
id: platform.supportVersions
}),
url: platform.downloadurl
}
)
}}
></span>
</Typography.Paragraph>
<h3 className="font-size-14 font-600">
2. <span dangerouslySetInnerHTML={{ __html: labels.step1 }}></span>
</h3>
<Typography.Paragraph style={{ paddingLeft: 14 }}>
<div className="flex">
<Typography.Text type="secondary">
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'resources.register.worker.step1'
})
}}
></span>
</Typography.Text>
</div>
</Typography.Paragraph>
<h3 className="m-t-10 font-size-14 font-600">
3. {labels.step2}{' '}
<span
className="font-size-12"
style={{ color: 'var(--ant-color-text-tertiary)' }}
dangerouslySetInnerHTML={{
__html: `${labels.step2Tips}`
}}
></span>
</h3>
<Typography.Paragraph style={{ paddingLeft: 14 }}>
<div className="flex">
<Space direction="vertical">
{registerWorkerSteps.map((step, index) => (
<Typography.Text key={index} type="secondary">
{index + 1}.{' '}
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage(
{ id: step },
{ url: window.location.origin }
)
}}
></span>
</Typography.Text>
))}
</Space>
</div>
</Typography.Paragraph>
<h3 className="m-b-0 m-t-10 font-size-14 font-600">4. {labels.step3}</h3>
</div>
);
};
export default AddWorkerMacOS;
@@ -1,125 +0,0 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils';
import HighlightCode from '@/components/highlight-code';
import CommandViewer from '@/pages/_components/command-viewer';
import { useIntl } from '@umijs/max';
import { Radio } from 'antd';
import React from 'react';
import { addWorkerGuide, containerInstallOptions } from '../config';
import './styles/installation.less';
type ViewModalProps = {
registrationInfo: {
token: string;
image: string;
server_url: string;
};
};
const npuOptions = [
{ label: '910B', value: 'npu' },
{ label: '310P', value: 'npu310p' }
];
const AddWorker: React.FC<ViewModalProps> = (props) => {
const { registrationInfo } = props || {};
const intl = useIntl();
const origin = window.location.origin;
const [activeKey, setActiveKey] = React.useState('cuda');
const [npuKey, setNpuKey] = React.useState('npu');
const versionInfo = getAtomStorage(GPUStackVersionAtom);
const code = React.useMemo(() => {
let version = versionInfo?.version;
if (!version || !versionInfo.isProduction) {
version = 'main';
}
let commandCode = addWorkerGuide[activeKey];
if (npuKey === 'npu310p' && activeKey === 'npu') {
commandCode = addWorkerGuide[npuKey];
}
const tag = activeKey === 'cuda' ? version : `${version}-${activeKey}`;
return commandCode?.registerWorker({
server: registrationInfo.server_url || origin,
tag: tag,
image: registrationInfo.image,
token: registrationInfo.token || '${token}',
workerip: '${workerip}'
});
}, [
versionInfo,
activeKey,
registrationInfo.token,
registrationInfo.server_url,
npuKey
]);
const handleOnChange = (value: string | number) => {
setNpuKey(value as string);
};
return (
<div className="container-install">
<h3 className="m-t-0 font-size-14 font-600">
1. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '}
<span
className="font-size-12"
style={{ color: 'var(--ant-color-text-tertiary)' }}
dangerouslySetInnerHTML={{
__html: `${intl.formatMessage({
id: 'resources.worker.add.step2.tips'
})}`
}}
></span>
</h3>
<div className="m-b-16">
<Radio.Group
block
options={containerInstallOptions}
defaultValue={activeKey}
value={activeKey}
optionType="button"
buttonStyle="solid"
onChange={(e) => setActiveKey(e.target.value)}
size="small"
/>
</div>
{activeKey === 'npu' && (
<div
className="m-b-8 text-tertiary"
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'resources.worker.cann.tips' })
}}
></div>
)}
{activeKey === 'npu' ? (
<CommandViewer
headerHeight={32}
copyText={code}
options={npuOptions}
defaultValue="npu"
onChange={handleOnChange}
lang="bash"
code={code.replace(/\\/g, '')}
></CommandViewer>
) : (
<HighlightCode
theme="dark"
code={code.replace(/\\/g, '')}
copyValue={code}
lang="bash"
></HighlightCode>
)}
<h3 className="m-b-0 m-t-10 font-size-14 font-600">
2. {intl.formatMessage({ id: 'resources.worker.add.step3' })}
</h3>
</div>
);
};
export default AddWorker;
+13 -2
View File
@@ -18,6 +18,7 @@ const GPUList: React.FC = () => {
extraStatus, extraStatus,
handlePageChange, handlePageChange,
handleTableChange, handleTableChange,
handleQueryChange,
handleSearch, handleSearch,
handleNameChange handleNameChange
} = useTableFetch<GPUDeviceItem>({ } = useTableFetch<GPUDeviceItem>({
@@ -44,6 +45,13 @@ const GPUList: React.FC = () => {
} }
}; };
const handleClusterChange = (value: number) => {
handleQueryChange({
page: 1,
cluster_id: value
});
};
const renderEmpty = (type?: string) => { const renderEmpty = (type?: string) => {
if (type !== 'Table') return; if (type !== 'Table') return;
return ( return (
@@ -79,13 +87,16 @@ const GPUList: React.FC = () => {
<PageBox> <PageBox>
<FilterBar <FilterBar
marginBottom={22} marginBottom={22}
marginTop={30}
buttonText={intl.formatMessage({ id: 'resources.button.create' })} buttonText={intl.formatMessage({ id: 'resources.button.create' })}
selectHolder={intl.formatMessage({ id: 'clusters.filterBy.cluster' })}
handleSearch={handleSearch} handleSearch={handleSearch}
handleInputChange={handleNameChange} handleInputChange={handleNameChange}
handleSelectChange={handleClusterChange}
showDeleteButton={false} showDeleteButton={false}
showPrimaryButton={false} showPrimaryButton={false}
width={{ input: 300 }} selectOptions={clusterList}
showSelect
width={{ input: 200 }}
></FilterBar> ></FilterBar>
<ConfigProvider renderEmpty={renderEmpty}> <ConfigProvider renderEmpty={renderEmpty}>
<Table <Table
@@ -1,70 +0,0 @@
import HighlightCode from '@/components/highlight-code';
import { useIntl } from '@umijs/max';
import React, { useMemo } from 'react';
import { addWorkerGuide } from '../config';
type ViewModalProps = { token: string };
const AddWorker: React.FC<ViewModalProps> = (props) => {
const intl = useIntl();
const origin = window.location.origin;
const labels = useMemo(
() => ({
step1: intl.formatMessage({ id: 'resources.worker.add.step1' }),
step2: intl.formatMessage({ id: 'resources.worker.add.step2' }),
step2Tips: intl.formatMessage({ id: 'resources.worker.add.step2.tips' }),
step3: intl.formatMessage({ id: 'resources.worker.add.step3' }),
linuxOrMac: intl.formatMessage({ id: 'resources.worker.linuxormaxos' })
}),
[intl]
);
return (
<div className="script-install">
<h3 className="font-size-14 font-600">
1. <span dangerouslySetInnerHTML={{ __html: labels.step1 }}></span>
</h3>
<h4 className="font-size-13">{labels.linuxOrMac}</h4>
<HighlightCode
code={addWorkerGuide.mac.getToken}
theme="dark"
></HighlightCode>
<h4 className="m-t-6 font-size-13">Windows </h4>
<HighlightCode
code={addWorkerGuide.win.getToken}
theme="dark"
></HighlightCode>
<h3 className="m-t-10 font-size-14 font-600">
2. {labels.step2}{' '}
<span
className="font-size-12"
style={{ color: 'var(--ant-color-text-tertiary)' }}
dangerouslySetInnerHTML={{
__html: `${labels.step2Tips}`
}}
></span>
</h3>
<h4 className="font-size-13">{labels.linuxOrMac}</h4>
<HighlightCode
code={addWorkerGuide.mac.registerWorker({
server: origin,
token: '${token}'
})}
theme="dark"
></HighlightCode>
<h4 className="m-t-6 font-size-13">Windows</h4>
<HighlightCode
theme="dark"
code={addWorkerGuide.win.registerWorker({
server: origin,
token: '${token}'
})}
></HighlightCode>
<h3 className="m-b-0 m-t-10 font-size-14 font-600">3. {labels.step3}</h3>
</div>
);
};
export default AddWorker;
+9 -13
View File
@@ -226,19 +226,7 @@ const Workers: React.FC = () => {
return ( return (
<> <>
<PageBox <PageBox>
ghost
footer={[
<TerminalTabs
height={300}
key="terminal-tabs"
terminals={terminals}
open={terminalModalStatus.open}
currentActive={terminalModalStatus.currentActive}
onClose={handleTerminalClose}
/>
]}
>
<FilterBar <FilterBar
showSelect={true} showSelect={true}
showPrimaryButton={false} showPrimaryButton={false}
@@ -291,6 +279,14 @@ const Workers: React.FC = () => {
setWorkerDetailStatus({ currentData: null, open: false }) setWorkerDetailStatus({ currentData: null, open: false })
} }
/> />
<TerminalTabs
height={300}
key="terminal-tabs"
terminals={terminals}
open={terminalModalStatus.open}
currentActive={terminalModalStatus.currentActive}
onClose={handleTerminalClose}
/>
</PageBox> </PageBox>
</> </>
); );