feat: worker_config
This commit is contained in:
@@ -90,5 +90,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
'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':
|
||||||
'{count} workers have been added to the cluster.'
|
'{count} workers have been added to the cluster.',
|
||||||
|
'clusters.create.serverUrl': 'Server URL',
|
||||||
|
'clusters.create.workerConfig': 'Worker Configuration'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
'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':
|
||||||
'{count} workers have been added to the cluster.'
|
'{count} workers have been added to the cluster.',
|
||||||
|
'clusters.create.serverUrl': 'Server URL',
|
||||||
|
'clusters.create.workerConfig': 'Worker Configuration'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -167,5 +169,7 @@ 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': '{count} workers have been added to the cluster.',
|
||||||
|
// 75. 'clusters.create.serverUrl': 'Server URL',
|
||||||
|
// 76. 'clusters.create.workerConfig': 'Worker Configuration'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ export default {
|
|||||||
'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':
|
||||||
'{count} workers have been added to the cluster.'
|
'{count} workers have been added to the cluster.',
|
||||||
|
'clusters.create.serverUrl': 'Server URL',
|
||||||
|
'clusters.create.workerConfig': 'Worker Configuration'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -98,5 +100,7 @@ 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': '{count} workers have been added to the cluster.',
|
||||||
|
// 6. 'clusters.create.serverUrl': 'Server URL',
|
||||||
|
// 7. 'clusters.create.workerConfig': 'Worker Configuration'
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|||||||
@@ -88,5 +88,7 @@ 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': '已将 {count} 个工作节点添加到集群中。',
|
||||||
|
'clusters.create.serverUrl': '服务器地址',
|
||||||
|
'clusters.create.workerConfig': '节点配置'
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-19
@@ -1,6 +1,4 @@
|
|||||||
import EditorWrap from '@/components/editor-wrap';
|
import EditorWrap from '@/components/editor-wrap';
|
||||||
import { PageAction } from '@/config';
|
|
||||||
import { PageActionType } from '@/config/types';
|
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import Editor, { loader } from '@monaco-editor/react';
|
import Editor, { loader } from '@monaco-editor/react';
|
||||||
import * as monaco from 'monaco-editor';
|
import * as monaco from 'monaco-editor';
|
||||||
@@ -11,15 +9,11 @@ import React, {
|
|||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef
|
useRef
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import createSchema from '../config/schema/create.json';
|
|
||||||
import updateBuiltinSchema from '../config/schema/update-builtin.json';
|
|
||||||
import udpateCustomSchema from '../config/schema/update-custom.json';
|
|
||||||
|
|
||||||
loader.config({ monaco });
|
loader.config({ monaco });
|
||||||
|
|
||||||
interface ViewerProps {
|
interface ViewerProps {
|
||||||
ref?: any;
|
ref?: any;
|
||||||
lang: string;
|
|
||||||
defaultLang?: string;
|
defaultLang?: string;
|
||||||
config?: any;
|
config?: any;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -28,22 +22,19 @@ interface ViewerProps {
|
|||||||
header?: React.ReactNode;
|
header?: React.ReactNode;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
variant?: 'bordered' | 'borderless';
|
variant?: 'bordered' | 'borderless';
|
||||||
actionStatus: {
|
schema?: any;
|
||||||
action: PageActionType;
|
|
||||||
isBuiltIn: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = 'inmemory://model/config.yaml';
|
const path = 'inmemory://model/config.yaml';
|
||||||
|
|
||||||
const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
const EditorInner: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
height = 380,
|
height = 380,
|
||||||
theme = 'vs-dark',
|
theme = 'vs-dark',
|
||||||
header,
|
header,
|
||||||
variant = 'borderless',
|
variant = 'borderless',
|
||||||
actionStatus,
|
schema,
|
||||||
placeholder
|
placeholder
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -59,12 +50,7 @@ const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
|||||||
{
|
{
|
||||||
uri: 'http://example.com/schema-name.json',
|
uri: 'http://example.com/schema-name.json',
|
||||||
fileMatch: [yamlUri.toString()],
|
fileMatch: [yamlUri.toString()],
|
||||||
schema:
|
schema: schema
|
||||||
actionStatus.action === PageAction.CREATE
|
|
||||||
? createSchema
|
|
||||||
: actionStatus.isBuiltIn
|
|
||||||
? updateBuiltinSchema
|
|
||||||
: udpateCustomSchema
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -151,4 +137,4 @@ const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ViewerEditor;
|
export default EditorInner;
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import useUserSettings from '@/hooks/use-user-settings';
|
||||||
|
import { ImportOutlined } from '@ant-design/icons';
|
||||||
|
import { loader } from '@monaco-editor/react';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, message, Typography, Upload } from 'antd';
|
||||||
|
import { RcFile } from 'antd/lib/upload';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef
|
||||||
|
} from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import EditorInner from './editor';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
loader.config({ monaco });
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--ant-color-border);
|
||||||
|
border-radius: var(--ant-border-radius);
|
||||||
|
.monaco-editor .scroll-decoration {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ErrorText = styled(Text)`
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background-color: var(--ant-color-bg-elevated);
|
||||||
|
border-radius: 0 0 var(--ant-border-radius) var(--ant-border-radius);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 40px;
|
||||||
|
padding-inline: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid var(--ant-color-border);
|
||||||
|
background-color: var(--ant-color-fill-tertiary);
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ViewerProps {
|
||||||
|
ref?: any;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
defaultLang?: string;
|
||||||
|
config?: any;
|
||||||
|
value: string;
|
||||||
|
height?: string | number;
|
||||||
|
placeholder?: string;
|
||||||
|
variant?: 'bordered' | 'borderless';
|
||||||
|
validateMessage?: React.ReactNode;
|
||||||
|
schema?: any;
|
||||||
|
onUpload?: (content: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const YamlEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
height = 380,
|
||||||
|
variant = 'borderless',
|
||||||
|
schema,
|
||||||
|
placeholder,
|
||||||
|
validateMessage,
|
||||||
|
title,
|
||||||
|
onUpload
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
const { userSettings } = useUserSettings();
|
||||||
|
|
||||||
|
const editorRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const setContent = (val: string) => {
|
||||||
|
editorRef.current?.setValue?.(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file: RcFile) => {
|
||||||
|
const isYaml =
|
||||||
|
file.type === 'application/x-yaml' ||
|
||||||
|
file.type === 'text/yaml' ||
|
||||||
|
file.name.endsWith('.yaml') ||
|
||||||
|
file.name.endsWith('.yml');
|
||||||
|
if (!isYaml) {
|
||||||
|
message.error('You can only upload YAML file!');
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const content = e.target?.result;
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
onUpload?.(content);
|
||||||
|
setContent(content);
|
||||||
|
} else {
|
||||||
|
message.error('Failed to read file content!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
// Prevent upload
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHeader = () => {
|
||||||
|
return (
|
||||||
|
<Header>
|
||||||
|
<span className="title">{title || 'YAML'}</span>
|
||||||
|
<Upload
|
||||||
|
name="file"
|
||||||
|
multiple={false}
|
||||||
|
beforeUpload={beforeUpload}
|
||||||
|
showUploadList={false}
|
||||||
|
accept=".yaml,.yml,text/yaml,application/x-yaml"
|
||||||
|
>
|
||||||
|
<Button icon={<ImportOutlined />} type="text" size="small">
|
||||||
|
{intl.formatMessage({ id: 'common.button.import' })}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
format: () => {
|
||||||
|
editorRef.current?.format();
|
||||||
|
},
|
||||||
|
getValue: () => {
|
||||||
|
return editorRef.current?.getValue?.();
|
||||||
|
},
|
||||||
|
setValue: (val: string) => {
|
||||||
|
editorRef.current?.setValue?.(val);
|
||||||
|
},
|
||||||
|
dispose: () => {
|
||||||
|
editorRef.current?.dispose?.();
|
||||||
|
},
|
||||||
|
validate() {
|
||||||
|
return editorRef.current?.validate();
|
||||||
|
},
|
||||||
|
editor: editorRef.current
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
editorRef.current?.format();
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<EditorInner
|
||||||
|
ref={editorRef}
|
||||||
|
header={renderHeader()}
|
||||||
|
variant={variant}
|
||||||
|
height={height}
|
||||||
|
theme={userSettings?.isDarkTheme ? 'vs-dark' : 'vs-light'}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
schema={schema}
|
||||||
|
/>
|
||||||
|
{validateMessage && (
|
||||||
|
<ErrorText type="danger">{validateMessage}</ErrorText>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default YamlEditor;
|
||||||
@@ -1,51 +1,17 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useUserSettings from '@/hooks/use-user-settings';
|
import YamlEditor from '@/pages/_components/yaml-editor';
|
||||||
import { ImportOutlined } from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, message, Typography, Upload } from 'antd';
|
|
||||||
import { RcFile } from 'antd/lib/upload';
|
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { yaml2Json, yamlTemplate } from '../config';
|
import { yaml2Json, yamlTemplate } from '../config';
|
||||||
import YamlEditor from './yaml-editor';
|
import createSchema from '../config/schema/create.json';
|
||||||
|
import updateBuiltinSchema from '../config/schema/update-builtin.json';
|
||||||
const { Text } = Typography;
|
import udpateCustomSchema from '../config/schema/update-custom.json';
|
||||||
|
|
||||||
const ErrorText = styled(Text)`
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 4px 6px;
|
|
||||||
background-color: var(--ant-color-bg-elevated);
|
|
||||||
border-radius: 0 0 var(--ant-border-radius) var(--ant-border-radius);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Header = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 40px;
|
|
||||||
padding-inline: 10px;
|
|
||||||
border-bottom: 1px solid var(--ant-color-border);
|
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid var(--ant-color-border);
|
|
||||||
border-radius: var(--ant-border-radius);
|
|
||||||
.monaco-editor .scroll-decoration {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ImportYAMLProps {
|
interface ImportYAMLProps {
|
||||||
ref?: any;
|
ref?: any;
|
||||||
@@ -60,7 +26,6 @@ interface ImportYAMLProps {
|
|||||||
const ImportYAML: React.FC<ImportYAMLProps> = forwardRef(
|
const ImportYAML: React.FC<ImportYAMLProps> = forwardRef(
|
||||||
({ actionStatus, content = '' }, ref) => {
|
({ actionStatus, content = '' }, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { userSettings } = useUserSettings();
|
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const [fileContent, setFileContent] = useState<string>(
|
const [fileContent, setFileContent] = useState<string>(
|
||||||
actionStatus.action === PageAction.CREATE ? yamlTemplate : content
|
actionStatus.action === PageAction.CREATE ? yamlTemplate : content
|
||||||
@@ -71,30 +36,6 @@ const ImportYAML: React.FC<ImportYAMLProps> = forwardRef(
|
|||||||
editorRef.current?.setValue?.(val);
|
editorRef.current?.setValue?.(val);
|
||||||
};
|
};
|
||||||
|
|
||||||
const beforeUpload = (file: RcFile) => {
|
|
||||||
const isYaml =
|
|
||||||
file.type === 'application/x-yaml' ||
|
|
||||||
file.type === 'text/yaml' ||
|
|
||||||
file.name.endsWith('.yaml') ||
|
|
||||||
file.name.endsWith('.yml');
|
|
||||||
if (!isYaml) {
|
|
||||||
message.error('You can only upload YAML file!');
|
|
||||||
}
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
const content = e.target?.result;
|
|
||||||
if (typeof content === 'string') {
|
|
||||||
setFileContent(content);
|
|
||||||
setContent(content);
|
|
||||||
} else {
|
|
||||||
message.error('Failed to read file content!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
// Prevent upload
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
const markers = editorRef.current?.validate();
|
const markers = editorRef.current?.validate();
|
||||||
if (markers && markers.length > 0) {
|
if (markers && markers.length > 0) {
|
||||||
@@ -152,25 +93,6 @@ const ImportYAML: React.FC<ImportYAMLProps> = forwardRef(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderHeader = () => {
|
|
||||||
return (
|
|
||||||
<Header>
|
|
||||||
<span className="title">YAML</span>
|
|
||||||
<Upload
|
|
||||||
name="file"
|
|
||||||
multiple={false}
|
|
||||||
beforeUpload={beforeUpload}
|
|
||||||
showUploadList={false}
|
|
||||||
accept=".yaml,.yml,text/yaml,application/x-yaml"
|
|
||||||
>
|
|
||||||
<Button icon={<ImportOutlined />} type="text" size="small">
|
|
||||||
{intl.formatMessage({ id: 'common.button.import' })}
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
</Header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getContent: () => {
|
getContent: () => {
|
||||||
return getContent();
|
return getContent();
|
||||||
@@ -182,18 +104,23 @@ const ImportYAML: React.FC<ImportYAMLProps> = forwardRef(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<YamlEditor
|
||||||
<YamlEditor
|
ref={editorRef}
|
||||||
ref={editorRef}
|
value={fileContent}
|
||||||
lang="yaml"
|
height={'calc(100vh - 260px)'}
|
||||||
actionStatus={actionStatus}
|
validateMessage={error}
|
||||||
theme={userSettings?.isDarkTheme ? 'vs-dark' : 'vs-light'}
|
onUpload={(content) => {
|
||||||
value={fileContent}
|
setError('');
|
||||||
height={'calc(100vh - 260px)'}
|
setContent(content);
|
||||||
header={renderHeader()}
|
}}
|
||||||
></YamlEditor>
|
schema={
|
||||||
{error && <ErrorText type="danger">{error}</ErrorText>}
|
actionStatus.action === PageAction.CREATE
|
||||||
</Container>
|
? createSchema
|
||||||
|
: actionStatus.isBuiltIn
|
||||||
|
? updateBuiltinSchema
|
||||||
|
: udpateCustomSchema
|
||||||
|
}
|
||||||
|
></YamlEditor>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealTextArea from '@/components/seal-form/seal-textarea';
|
import SealTextArea from '@/components/seal-form/seal-textarea';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
|
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||||
|
import { json2Yaml, yaml2Json } from '@/pages/backends/config';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
|
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||||
@@ -9,6 +11,7 @@ import {
|
|||||||
ClusterFormData as FormData,
|
ClusterFormData as FormData,
|
||||||
ClusterListItem as ListItem
|
ClusterListItem as ListItem
|
||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
|
import AdvanceConfig from '../step-forms/advance-config';
|
||||||
import CloudProvider from './cloud-provider-form';
|
import CloudProvider from './cloud-provider-form';
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
@@ -23,12 +26,27 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
|||||||
({ action, provider, currentData, credentialList, onFinish }, ref) => {
|
({ action, provider, currentData, credentialList, onFinish }, ref) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [activeKey, setActiveKey] = React.useState<string[]>([]);
|
||||||
|
const advanceConfigRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
const handleOnCollapseChange = (keys: string | string[]) => {
|
||||||
|
setActiveKey(Array.isArray(keys) ? keys : [keys]);
|
||||||
|
if (currentData && advanceConfigRef.current) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentData) {
|
if (currentData) {
|
||||||
form.setFieldsValue(currentData);
|
form.setFieldsValue(currentData);
|
||||||
|
|
||||||
|
if (advanceConfigRef.current) {
|
||||||
|
const workerConfigYaml = json2Yaml({
|
||||||
|
worker_config: currentData.worker_config || {}
|
||||||
|
});
|
||||||
|
advanceConfigRef.current?.setYamlValue(workerConfigYaml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [currentData]);
|
}, [currentData, advanceConfigRef.current]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
resetFields: () => {
|
resetFields: () => {
|
||||||
@@ -44,7 +62,15 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
|||||||
return form.getFieldsValue();
|
return form.getFieldsValue();
|
||||||
},
|
},
|
||||||
validateFields: async () => {
|
validateFields: async () => {
|
||||||
return await form.validateFields();
|
const values = await form.validateFields();
|
||||||
|
const workerConfig = yaml2Json(
|
||||||
|
advanceConfigRef.current?.getYamlValue()
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...values,
|
||||||
|
...workerConfig
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -91,6 +117,24 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
|||||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
></SealTextArea>
|
></SealTextArea>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<CollapsePanel
|
||||||
|
accordion={false}
|
||||||
|
activeKey={activeKey}
|
||||||
|
onChange={handleOnCollapseChange}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'advanceConfig',
|
||||||
|
label: intl.formatMessage({ id: 'resources.form.advanced' }),
|
||||||
|
forceRender: true,
|
||||||
|
children: (
|
||||||
|
<AdvanceConfig
|
||||||
|
action={action}
|
||||||
|
ref={advanceConfigRef}
|
||||||
|
></AdvanceConfig>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></CollapsePanel>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export interface ClusterListItem {
|
|||||||
name: string;
|
name: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
worker_config: Record<string, any>;
|
||||||
provider: ProviderType;
|
provider: ProviderType;
|
||||||
credential_id: number;
|
credential_id: number;
|
||||||
zone: string;
|
zone: string;
|
||||||
@@ -74,5 +75,7 @@ export interface ClusterFormData {
|
|||||||
credential_id: number;
|
credential_id: number;
|
||||||
zone: string;
|
zone: string;
|
||||||
region: string;
|
region: string;
|
||||||
|
server_url?: string;
|
||||||
|
worker_config?: Record<string, any>;
|
||||||
worker_pools?: NodePoolFormData[];
|
worker_pools?: NodePoolFormData[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"worker_config": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable debug mode"
|
||||||
|
},
|
||||||
|
"cache_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Cache directory path"
|
||||||
|
},
|
||||||
|
"log_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Log directory path"
|
||||||
|
},
|
||||||
|
"bin_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Binary directory path"
|
||||||
|
},
|
||||||
|
"system_default_container_registry": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Default container registry"
|
||||||
|
},
|
||||||
|
"image_name_override": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Override image name"
|
||||||
|
},
|
||||||
|
"image_repo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Container image repository"
|
||||||
|
},
|
||||||
|
"gateway_mode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Gateway mode",
|
||||||
|
"enum": ["worker", "gateway", "disabled"]
|
||||||
|
},
|
||||||
|
"gateway_kubeconfig": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to kubeconfig used by gateway"
|
||||||
|
},
|
||||||
|
"gateway_concurrency": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"description": "Gateway concurrency limit"
|
||||||
|
},
|
||||||
|
"service_discovery_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service discovery name"
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Kubernetes namespace"
|
||||||
|
},
|
||||||
|
"huggingface_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Hugging Face access token"
|
||||||
|
},
|
||||||
|
"disable_worker_metrics": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Disable worker metrics collection"
|
||||||
|
},
|
||||||
|
"worker_port": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 65535,
|
||||||
|
"description": "Worker service port"
|
||||||
|
},
|
||||||
|
"worker_metrics_port": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 65535,
|
||||||
|
"description": "Worker metrics port"
|
||||||
|
},
|
||||||
|
"service_port_range": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\d+-\\d+$",
|
||||||
|
"description": "Service port range, e.g. 30000-31000"
|
||||||
|
},
|
||||||
|
"ray_port_range": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\d+-\\d+$",
|
||||||
|
"description": "Ray port range, e.g. 20000-20100"
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Additional resource definitions",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pipx_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "pipx installation path"
|
||||||
|
},
|
||||||
|
"tools_download_base_url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Base URL for downloading tools"
|
||||||
|
},
|
||||||
|
"enable_hf_transfer": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable Hugging Face transfer acceleration"
|
||||||
|
},
|
||||||
|
"enable_hf_xet": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable Hugging Face Xet support"
|
||||||
|
},
|
||||||
|
"proxy_mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["worker", "system", "disabled"],
|
||||||
|
"description": "Proxy mode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
export default `# This is a template for worker_config.
|
||||||
|
|
||||||
|
worker_config:
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
# directories
|
||||||
|
cache_dir:
|
||||||
|
log_dir:
|
||||||
|
bin_dir:
|
||||||
|
|
||||||
|
# container & image
|
||||||
|
system_default_container_registry:
|
||||||
|
image_repo: gpustack/worker
|
||||||
|
image_name_override: ""
|
||||||
|
|
||||||
|
# gateway
|
||||||
|
gateway_mode:
|
||||||
|
gateway_concurrency: 0
|
||||||
|
gateway_kubeconfig: ""
|
||||||
|
|
||||||
|
# service & networking
|
||||||
|
service_discovery_name:
|
||||||
|
namespace: default
|
||||||
|
worker_port:
|
||||||
|
worker_metrics_port:
|
||||||
|
service_port_range:
|
||||||
|
ray_port_range:
|
||||||
|
|
||||||
|
# resources
|
||||||
|
resources:
|
||||||
|
gpu:
|
||||||
|
count:
|
||||||
|
type:
|
||||||
|
cpu:
|
||||||
|
limit:
|
||||||
|
memory:
|
||||||
|
limit:
|
||||||
|
|
||||||
|
# huggingface
|
||||||
|
huggingface_token: ""
|
||||||
|
enable_hf_transfer: true
|
||||||
|
enable_hf_xet: true
|
||||||
|
|
||||||
|
# metrics
|
||||||
|
disable_worker_metrics: false
|
||||||
|
|
||||||
|
# tools & runtime
|
||||||
|
pipx_path:
|
||||||
|
tools_download_base_url:
|
||||||
|
|
||||||
|
# proxy
|
||||||
|
proxy_mode: worker
|
||||||
|
`;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import YamlEditor from '@/pages/_components/yaml-editor';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
|
import { ClusterFormData as FormData } from '../config/types';
|
||||||
|
import schema from '../config/worker-config.json';
|
||||||
|
import yamlTemplate from '../config/yaml-template';
|
||||||
|
|
||||||
|
const ClusterAdvanceConfig: React.FC<{ action: PageActionType; ref?: any }> =
|
||||||
|
forwardRef(({ action }, ref) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const intl = useIntl();
|
||||||
|
const editorRef = React.useRef<any>(null);
|
||||||
|
const [fileContent, setFileContent] = React.useState<string>('');
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getYamlValue: () => {
|
||||||
|
return editorRef.current?.getValue();
|
||||||
|
},
|
||||||
|
setYamlValue: (values: any) => {
|
||||||
|
console.log('setYamlValue:', values, editorRef.current);
|
||||||
|
editorRef.current?.setValue(values);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="server_url"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label={intl.formatMessage({ id: 'clusters.create.serverUrl' })}
|
||||||
|
required={false}
|
||||||
|
trim={true}
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
hidden
|
||||||
|
name="worker_config"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.TextArea
|
||||||
|
required={false}
|
||||||
|
trim={false}
|
||||||
|
></SealInput.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
<YamlEditor
|
||||||
|
ref={editorRef}
|
||||||
|
title={`${intl.formatMessage({ id: 'clusters.create.workerConfig' })} YAML`}
|
||||||
|
value={fileContent}
|
||||||
|
height={300}
|
||||||
|
onUpload={(content) => {
|
||||||
|
setFileContent(content);
|
||||||
|
}}
|
||||||
|
schema={schema}
|
||||||
|
placeholder={yamlTemplate}
|
||||||
|
></YamlEditor>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ClusterAdvanceConfig;
|
||||||
Reference in New Issue
Block a user