feat: backend entrypoint

This commit is contained in:
jialin
2025-12-19 14:40:20 +08:00
parent 14d944ad2f
commit ad048a92ed
21 changed files with 277 additions and 88 deletions
+17 -2
View File
@@ -1,4 +1,8 @@
import { WarningFilled } from '@ant-design/icons'; import {
CheckCircleFilled,
LoadingOutlined,
WarningFilled
} from '@ant-design/icons';
import { Typography } from 'antd'; import { Typography } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -122,6 +126,17 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
overlayScrollerProps = {} overlayScrollerProps = {}
} = props; } = props;
const { styles } = useStyles(); const { styles } = useStyles();
const renderIcon = () => {
if (type === 'transition') {
return <LoadingOutlined />;
}
if (type === 'success') {
return <CheckCircleFilled />;
}
return <WarningFilled />;
};
return ( return (
<> <>
{message ? ( {message ? (
@@ -139,7 +154,7 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
> >
<div className={classNames('title', type)}> <div className={classNames('title', type)}>
<span className={classNames('info-icon', type)}> <span className={classNames('info-icon', type)}>
{icon ?? <WarningFilled />} {icon ?? renderIcon()}
</span> </span>
</div> </div>
{title && ( {title && (
+1 -1
View File
@@ -102,7 +102,7 @@ const SealTextArea: React.FC<InputTextareaProps & SealFormItemProps> = (
<InputWrapper> <InputWrapper>
<Wrapper <Wrapper
status={status} status={status}
label={<LabelWrapper>{label}</LabelWrapper>} label={label && <LabelWrapper>{label}</LabelWrapper>}
isFocus={alwaysFocus || isFocus} isFocus={alwaysFocus || isFocus}
required={required} required={required}
description={description} description={description}
+75
View File
@@ -0,0 +1,75 @@
import { Switch, Tooltip } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import LabelInfo from '../seal-form/components/label-info';
const SwitchContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px;
border: 1px solid var(--ant-color-border);
border-radius: var(--ant-border-radius);
padding: 12px 14px;
min-height: 54px;
`;
const LabelContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`;
interface SwitchInputProps {
label?: React.ReactNode;
description?: string;
checked?: boolean;
defaultChecked?: boolean;
onChange?: (checked: boolean) => void;
children?: React.ReactNode;
style?: React.CSSProperties;
alwaysShowChildren?: boolean;
btnTips?: React.ReactNode;
size?: 'small' | 'default';
}
const SwitchInput: React.FC<SwitchInputProps> = (props) => {
const {
label,
description,
checked,
defaultChecked,
onChange,
children,
style,
alwaysShowChildren = true,
size,
btnTips
} = props;
const [internalChecked, setInternalChecked] = useState<boolean>(
defaultChecked || false
);
const handleChange = (checked: boolean) => {
setInternalChecked(checked);
onChange?.(checked);
};
return (
<SwitchContainer style={style}>
<LabelContainer>
<LabelInfo label={label} description={description} />
<Tooltip title={btnTips}>
<Switch
size={size}
checked={checked !== undefined ? checked : internalChecked}
onChange={handleChange}
/>
</Tooltip>
</LabelContainer>
{(alwaysShowChildren || internalChecked) && children}
</SwitchContainer>
);
};
export default SwitchInput;
+1 -1
View File
@@ -203,7 +203,7 @@ body {
// form item help // form item help
.ant-form-item-with-help .ant-form-item-explain { .ant-form-item-with-help .ant-form-item-explain {
padding-left: 16px; // padding-left: 16px;
} }
// icon // icon
+5 -1
View File
@@ -34,5 +34,9 @@ export default {
'The custom backend name must end with "-custom".', 'The custom backend name must end with "-custom".',
'backend.quickConfig': 'Quick Config', 'backend.quickConfig': 'Quick Config',
'backend.version.default.not.exists': 'backend.version.default.not.exists':
'The default version does not exist in {versions}.' 'The default version does not exist in {versions}.',
'backend.replaceEntrypoint': 'Override Image Entrypoint',
'backend.entrypoint': 'Image Entrypoint',
'backend.entrypoint.tips':
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.'
}; };
+4 -2
View File
@@ -89,8 +89,10 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.button.genToken': 'clusters.button.genToken':
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`, 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
'clusters.addworker.message.success': 'clusters.addworker.message.success_single':
'{count} workers have been added to the cluster.', '{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple':
'{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'Server URL', 'clusters.create.serverUrl': 'Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName': 'Worker Container Name',
+5 -1
View File
@@ -34,5 +34,9 @@ export default {
'The custom backend name must end with "-custom".', 'The custom backend name must end with "-custom".',
'backend.quickConfig': 'Quick Config', 'backend.quickConfig': 'Quick Config',
'backend.version.default.not.exists': 'backend.version.default.not.exists':
'The default version does not exist in {versions}.' 'The default version does not exist in {versions}.',
'backend.replaceEntrypoint': 'Override Image Entrypoint',
'backend.entrypoint': 'Image Entrypoint',
'backend.entrypoint.tips':
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.'
}; };
+10 -7
View File
@@ -89,8 +89,10 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.button.genToken': 'clusters.button.genToken':
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`, 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
'clusters.addworker.message.success': 'clusters.addworker.message.success_single':
'{count} workers have been added to the cluster.', '{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple':
'{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'Server URL', 'clusters.create.serverUrl': 'Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName': 'Worker Container Name',
@@ -179,11 +181,12 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
// 71. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', // 71. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount',
// 72. 'clusters.addworker.cacheVolume.tips': 'If you want to customize the model cache directory, you can specify the path to mount it.', // 72. 'clusters.addworker.cacheVolume.tips': 'If you want to customize the model cache directory, you can specify the path to mount it.',
// 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)', // 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)',
// 74. 'clusters.addworker.message.success': '{count} workers have been added to the cluster.', // 74. 'clusters.addworker.message.success_single': '{count} new worker has been added to the cluster.',
// 75. 'clusters.create.serverUrl': 'Server URL', // 75. 'clusters.addworker.message.success_multiple': '{count} new workers have been added to the cluster.',
// 76. 'clusters.create.workerConfig': 'Worker Configuration' // 76. 'clusters.create.serverUrl': 'Server URL',
// 75. 'clusters.addworker.containerName': 'Worker Container Name', // 77. 'clusters.create.workerConfig': 'Worker Configuration'
// 76. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.', // 78. 'clusters.addworker.containerName': 'Worker Container Name',
// 79. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.',
// 77. 'clusters.addworker.dataVolume': 'GPUStack Data Volume', // 77. 'clusters.addworker.dataVolume': 'GPUStack Data Volume',
// 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.', // 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.',
// 79. 'clusters.table.ip.internal': 'Internal', // 79. 'clusters.table.ip.internal': 'Internal',
+10 -2
View File
@@ -34,9 +34,17 @@ export default {
'Пользовательское название бэкенда должно оканчиваться на «-custom».', 'Пользовательское название бэкенда должно оканчиваться на «-custom».',
'backend.quickConfig': 'Быстрая настройка', 'backend.quickConfig': 'Быстрая настройка',
'backend.version.default.not.exists': 'backend.version.default.not.exists':
'The default version does not exist in {versions}.' 'The default version does not exist in {versions}.',
'backend.replaceEntrypoint': 'Override Image Entrypoint',
'backend.entrypoint': 'Image Entrypoint',
'backend.entrypoint.tips':
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'backend.version.default.not.exists': 'The default version does not exist in {versions}.' // 1. 'backend.version.default.not.exists': 'The default version does not exist in {versions}.',
// 2. 'backend.replaceEntrypoint': 'Override Image Entrypoint',
// 3. 'backend.entrypoint': 'Image Entrypoint',
// 4. 'backend.entrypoint.tips':
// 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+12 -9
View File
@@ -89,8 +89,10 @@ export default {
'clusters.button.genToken': 'clusters.button.genToken':
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`, 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
'clusters.addworker.message.success': 'clusters.addworker.message.success_single':
'{count} workers have been added to the cluster.', '{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple':
'{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'Server URL', 'clusters.create.serverUrl': 'Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName': 'Worker Container Name',
@@ -110,13 +112,14 @@ export default {
// 2. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', // 2. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
// 3. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', // 3. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount',
// 4. 'clusters.addworker.cacheVolume.tips': 'If you want to customize the model cache directory, you can specify the path to mount it.', // 4. 'clusters.addworker.cacheVolume.tips': 'If you want to customize the model cache directory, you can specify the path to mount it.',
// 5. 'clusters.addworker.message.success': '{count} workers have been added to the cluster.', // 5. 'clusters.addworker.message.success_single': '{count} new worker has been added to the cluster.',
// 6. 'clusters.create.serverUrl': 'Server URL', // 6. 'clusters.addworker.message.success_multiple': '{count} new workers have been added to the cluster.',
// 7. 'clusters.create.workerConfig': 'Worker Configuration' // 7. 'clusters.create.serverUrl': 'Server URL',
// 6. 'clusters.addworker.containerName': 'Worker Container Name', // 8. 'clusters.create.workerConfig': 'Worker Configuration'
// 7. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.', // 9. 'clusters.addworker.containerName': 'Worker Container Name',
// 8. 'clusters.addworker.dataVolume': 'GPUStack Data Volume', // 10. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.',
// 9. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.', // 11. 'clusters.addworker.dataVolume': 'GPUStack Data Volume',
// 12. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.',
// 10. 'clusters.table.ip.internal': 'Internal', // 10. 'clusters.table.ip.internal': 'Internal',
// 11. 'clusters.table.ip.external': 'External', // 11. 'clusters.table.ip.external': 'External',
// 12. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.' // 12. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.'
+5 -1
View File
@@ -31,5 +31,9 @@ export default {
'backend.version.no.tips': '内置后端自定义版本名称必须以 "-custom" 结尾。', 'backend.version.no.tips': '内置后端自定义版本名称必须以 "-custom" 结尾。',
'backend.backend.rules.custom': '自定义后端名称须以 "-custom" 结尾。', 'backend.backend.rules.custom': '自定义后端名称须以 "-custom" 结尾。',
'backend.quickConfig': '快速配置', 'backend.quickConfig': '快速配置',
'backend.version.default.not.exists': '默认版本不存在于 {versions}。' 'backend.version.default.not.exists': '默认版本不存在于 {versions}。',
'backend.replaceEntrypoint': '覆盖镜像入口命令',
'backend.entrypoint': '镜像入口命令',
'backend.entrypoint.tips':
'如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。'
}; };
+4 -1
View File
@@ -88,7 +88,10 @@ export default {
'需要创建令牌?点击<a href="{link}" target="_blank">这里</a>。', '需要创建令牌?点击<a href="{link}" target="_blank">这里</a>。',
'clusters.addworker.amdNotes-01': 'clusters.addworker.amdNotes-01':
'如果 <span class="bold-text">/opt/rocm</span> 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:<span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>。', '如果 <span class="bold-text">/opt/rocm</span> 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:<span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>。',
'clusters.addworker.message.success': '已将 {count} 个工作节点添加到集群中。', 'clusters.addworker.message.success_single':
'已将 {count} 个新节点添加到集群中。',
'clusters.addworker.message.success_multiple':
'已将 {count} 个新节点添加到集群中。',
'clusters.create.serverUrl': '服务器地址', 'clusters.create.serverUrl': '服务器地址',
'clusters.create.workerConfig': '节点配置', 'clusters.create.workerConfig': '节点配置',
'clusters.addworker.containerName': '节点容器名称', 'clusters.addworker.containerName': '节点容器名称',
+2 -1
View File
@@ -84,7 +84,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
acc[curr.version_no] = { acc[curr.version_no] = {
custom_framework: curr.custom_framework, custom_framework: curr.custom_framework,
image_name: curr.image_name, image_name: curr.image_name,
run_command: curr.run_command run_command: curr.run_command,
entrypoint: curr.entrypoint
}; };
} }
return acc; return acc;
+1
View File
@@ -4,6 +4,7 @@ export interface VersionConfigs {
is_default: boolean; is_default: boolean;
built_in_frameworks?: string[]; built_in_frameworks?: string[];
custom_framework?: string; custom_framework?: string;
entrypoint?: string;
version_no?: string; version_no?: string;
is_built_in?: boolean; is_built_in?: boolean;
} }
+25 -2
View File
@@ -2,6 +2,7 @@ import CollapsibleContainer from '@/components/collapse-container';
import BaseSelect from '@/components/seal-form/base/select'; import BaseSelect from '@/components/seal-form/base/select';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import SealTextArea from '@/components/seal-form/seal-textarea';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons'; import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
@@ -342,14 +343,36 @@ const VersionsForm: React.FC<AddModalProps> = ({
required required
/> />
</Form.Item> </Form.Item>
<Form.Item name={[name, 'entrypoint']}>
<SealInput.TextArea
allowClear
description={intl.formatMessage({
id: 'backend.entrypoint.tips'
})}
label={intl.formatMessage({
id: 'backend.replaceEntrypoint'
})}
></SealInput.TextArea>
</Form.Item>
<Form.Item <Form.Item
name={[name, 'run_command']} name={[name, 'run_command']}
style={{ marginBottom: 0 }} style={{ marginBottom: 0 }}
> >
<SealInput.TextArea <SealTextArea
allowClear allowClear
alwaysFocus={true}
description={intl.formatMessage({
id: 'backend.form.defaultExecuteCommand.tips'
})}
placeholder={intl.formatMessage(
{ id: 'common.help.eg' },
{
content:
'vllm serve {{model_path}} --port {{port}} --host {{worker_ip}} --served-model-name {{model_name}}'
}
)}
label={intl.formatMessage({ id: 'backend.runCommand' })} label={intl.formatMessage({ id: 'backend.runCommand' })}
></SealInput.TextArea> ></SealTextArea>
</Form.Item> </Form.Item>
</CollapsibleContainer> </CollapsibleContainer>
</div> </div>
@@ -8,6 +8,8 @@ interface AddWorkerContextProps {
clusterLoading?: boolean; clusterLoading?: boolean;
provider: ProviderType; provider: ProviderType;
stepList: string[]; stepList: string[];
actionSource?: 'modal' | 'page';
onCancel?: () => void;
onClusterChange?: (value: number, row?: any) => void; onClusterChange?: (value: number, row?: any) => void;
collapseKey: Set<string>; collapseKey: Set<string>;
onToggle: (open: boolean, key: string) => void; onToggle: (open: boolean, key: string) => void;
@@ -1,4 +1,4 @@
import AlertInfoBlock from '@/components/alert-info/block'; import AlertBlockInfo from '@/components/alert-info/block';
import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message'; import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message';
import { ExclamationCircleFilled } from '@ant-design/icons'; import { ExclamationCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -36,6 +36,7 @@ type AddWorkerProps = {
clusterLoading?: boolean; clusterLoading?: boolean;
stepList: StepName[]; stepList: StepName[];
onClusterChange?: (value: number, row?: any) => void; onClusterChange?: (value: number, row?: any) => void;
onCancel?: () => void;
registrationInfo: { registrationInfo: {
token: string; token: string;
image: string; image: string;
@@ -57,6 +58,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
clusterList, clusterList,
clusterLoading, clusterLoading,
stepList = [], stepList = [],
onCancel,
onClusterChange onClusterChange
} = props || {}; } = props || {};
const intl = useIntl(); const intl = useIntl();
@@ -82,12 +84,28 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
React.useEffect(() => { React.useEffect(() => {
// this effect is only triggered when used in cluster create page // this effect is only triggered when used in cluster create page
console.log('actionSource:', actionSource);
if (actionSource === 'page') { if (actionSource === 'page') {
createModelsChunkRequest(); createModelsChunkRequest();
} }
}, [actionSource]); }, [actionSource]);
const renderMessage = (count: number) => {
if (count === 1) {
return intl.formatMessage(
{
id: 'clusters.addworker.message.success_single'
},
{ count: addedCount }
);
}
return intl.formatMessage(
{
id: 'clusters.addworker.message.success_multiple'
},
{ count: addedCount }
);
};
return ( return (
<AddWorkerContext.Provider <AddWorkerContext.Provider
value={{ value={{
@@ -96,7 +114,9 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
provider, provider,
stepList: stepList, stepList: stepList,
collapseKey, collapseKey,
actionSource,
onToggle, onToggle,
onCancel,
onClusterChange: handleOnClusterChange, onClusterChange: handleOnClusterChange,
registrationInfo, registrationInfo,
summary, summary,
@@ -110,7 +130,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
)} )}
{stepList.includes(StepNamesMap.SelectCluster) && {stepList.includes(StepNamesMap.SelectCluster) &&
!clusterList?.length && ( !clusterList?.length && (
<AlertInfoBlock <AlertBlockInfo
maxHeight={200} maxHeight={200}
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
type="warning" type="warning"
@@ -118,7 +138,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
message={intl.formatMessage({ message={intl.formatMessage({
id: 'resources.worker.noCluster.tips' id: 'resources.worker.noCluster.tips'
})} })}
></AlertInfoBlock> ></AlertBlockInfo>
)} )}
{/* render the steps only when there is at least one cluster available or cluster selection is not required */} {/* render the steps only when there is at least one cluster available or cluster selection is not required */}
@@ -142,14 +162,14 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
)} )}
{addedCount > 0 && ( {addedCount > 0 && (
<Alert <Alert
style={{ width: 'max-content' }} style={{
message={intl.formatMessage( textAlign: 'left',
{ borderColor: 'var(--ant-color-success)',
id: 'clusters.addworker.message.success' width: '100%'
}, }}
{ count: addedCount }
)}
type="success" type="success"
message={renderMessage(addedCount)}
closable
/> />
)} )}
</Container> </Container>
@@ -1,7 +1,7 @@
import ScrollerModal from '@/components/scroller-modal'; import ScrollerModal from '@/components/scroller-modal';
import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message'; import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Alert, Button } from 'antd'; import { Alert } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { queryClusterToken } from '../../apis'; import { queryClusterToken } from '../../apis';
@@ -100,6 +100,23 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
} }
}, [open]); }, [open]);
const renderMessage = (count: number) => {
if (count === 1) {
return intl.formatMessage(
{
id: 'clusters.addworker.message.success_single'
},
{ count: addedCount }
);
}
return intl.formatMessage(
{
id: 'clusters.addworker.message.success_multiple'
},
{ count: addedCount }
);
};
return ( return (
<ScrollerModal <ScrollerModal
title={title} title={title}
@@ -115,32 +132,23 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
maxContentHeight={'max(calc(100vh - 200px), 600px)'} maxContentHeight={'max(calc(100vh - 200px), 600px)'}
footer={ footer={
<Footer> <Footer>
<span className="tips"> {addedCount > 0 && (
{addedCount > 0 && ( <Alert
<Alert style={{
message={intl.formatMessage( textAlign: 'left',
{ borderColor: 'var(--ant-color-success)',
id: 'clusters.addworker.message.success' width: '100%'
}, }}
{ count: addedCount } type="success"
)} message={renderMessage(addedCount)}
type="success" closable
/> />
)} )}
</span>
<Button
type="primary"
onClick={onCancel}
style={{
minWidth: 88
}}
>
{intl.formatMessage({ id: 'common.button.done' })}
</Button>
</Footer> </Footer>
} }
> >
<AddWorkerStep <AddWorkerStep
onCancel={onCancel}
actionSource={'modal'} actionSource={'modal'}
stepList={stepList} stepList={stepList}
provider={provider} provider={provider}
@@ -29,6 +29,8 @@ const Box = styled.div`
const ButtonWrapper = styled.div` const ButtonWrapper = styled.div`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
gap: 16px;
margin-top: 16px; margin-top: 16px;
width: 100%; width: 100%;
`; `;
@@ -41,7 +43,8 @@ const StepCollapse: React.FC<StepItemProps> = ({
...rest ...rest
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const { collapseKey, onToggle, stepList } = useAddWorkerContext(); const { collapseKey, onToggle, stepList, actionSource, onCancel } =
useAddWorkerContext();
const handleOnNext = async () => { const handleOnNext = async () => {
const res = await beforeNext?.(); const res = await beforeNext?.();
@@ -51,8 +54,16 @@ const StepCollapse: React.FC<StepItemProps> = ({
onToggle(true, nextName); onToggle(true, nextName);
}; };
const handleOnPrevious = () => {
// find the previous step and open it
const previousName = stepList[stepList.indexOf(name) - 1];
onToggle(true, previousName);
};
const isLastStep = stepList.indexOf(name) === stepList.length - 1; const isLastStep = stepList.indexOf(name) === stepList.length - 1;
const isFirstStep = stepList.indexOf(name) === 0;
return ( return (
<Box <Box
className={ className={
@@ -75,13 +86,31 @@ const StepCollapse: React.FC<StepItemProps> = ({
{...rest} {...rest}
> >
{children} {children}
{!isLastStep && (
<ButtonWrapper> <ButtonWrapper>
{!isFirstStep && (
<Button onClick={handleOnPrevious}>
{intl.formatMessage({ id: 'common.button.prev' })}
</Button>
)}
{!isLastStep && (
<Button type="primary" onClick={handleOnNext}> <Button type="primary" onClick={handleOnNext}>
{intl.formatMessage({ id: 'common.button.next' })} {intl.formatMessage({ id: 'common.button.next' })}
</Button> </Button>
</ButtonWrapper> )}
)} {isLastStep && actionSource === 'modal' && (
<Button
type="primary"
onClick={onCancel}
style={{
minWidth: 88
}}
>
{intl.formatMessage({ id: 'common.button.done' })}
</Button>
)}
</ButtonWrapper>
</CollapsibleContainer> </CollapsibleContainer>
</Box> </Box>
); );
@@ -25,7 +25,7 @@ const ClusterAdvanceConfig: React.FC<{
return editorRef.current?.getValue(); return editorRef.current?.getValue();
}, },
setYamlValue: (values: any) => { setYamlValue: (values: any) => {
editorRef.current?.setValue(values); editorRef.current?.setValue(values || yamlTemplate);
} }
})); }));
@@ -1,10 +1,5 @@
import AlertBlockInfo from '@/components/alert-info/block'; import AlertBlockInfo from '@/components/alert-info/block';
import { import { CloseOutlined } from '@ant-design/icons';
CheckCircleFilled,
CloseOutlined,
LoadingOutlined,
WarningFilled
} from '@ant-design/icons';
import { Button } from 'antd'; import { Button } from 'antd';
import { isArray } from 'lodash'; import { isArray } from 'lodash';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
@@ -98,16 +93,6 @@ const CompatibilityAlert: React.FC<CompatibilityAlertProps> = (props) => {
return ''; return '';
}, [message, show, handleLinkMessage]); }, [message, show, handleLinkMessage]);
const renderIcon = useMemo(() => {
if (type === 'transition') {
return <LoadingOutlined />;
}
if (type === 'success') {
return <CheckCircleFilled />;
}
return <WarningFilled />;
}, [type]);
return ( return (
show && show &&
!!message && ( !!message && (
@@ -118,7 +103,6 @@ const CompatibilityAlert: React.FC<CompatibilityAlertProps> = (props) => {
title={title} title={title}
contentStyle={contentStyle} contentStyle={contentStyle}
type={type || 'warning'} type={type || 'warning'}
icon={renderIcon}
style={{ style={{
paddingInlineEnd: showClose ? 20 : 16 paddingInlineEnd: showClose ? 20 : 16
}} }}