fix: cluster value setting, volumes setting
This commit is contained in:
@@ -172,9 +172,8 @@ export default function CollapsibleContainer({
|
|||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
style={{
|
style={{
|
||||||
maxHeight: height,
|
height: height,
|
||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
// transition: collapsible ? 'max-height 0.2s ease' : 'none'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ paddingTop: 8 }}>{children}</div>
|
<div style={{ paddingTop: 8 }}>{children}</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { FormWidgetProps } from '../config/types';
|
|||||||
const FormWidget: React.FC<
|
const FormWidget: React.FC<
|
||||||
FormWidgetProps & {
|
FormWidgetProps & {
|
||||||
onChange?: (data: any) => void;
|
onChange?: (data: any) => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
> = ({
|
> = ({
|
||||||
widget,
|
widget,
|
||||||
@@ -17,8 +18,10 @@ const FormWidget: React.FC<
|
|||||||
value,
|
value,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
|
status,
|
||||||
checked,
|
checked,
|
||||||
readOnly: disabled,
|
isInFormItems,
|
||||||
|
disabled,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
const Component = ComponentsMap[widget];
|
const Component = ComponentsMap[widget];
|
||||||
@@ -38,6 +41,8 @@ const FormWidget: React.FC<
|
|||||||
min,
|
min,
|
||||||
max
|
max
|
||||||
}}
|
}}
|
||||||
|
status={status}
|
||||||
|
isInFormItems={isInFormItems}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
options={options || optionList}
|
options={options || optionList}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { MinusOutlined } from '@ant-design/icons';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { statusType } from '../config/types';
|
||||||
import FormWidget from './form-widget';
|
import FormWidget from './form-widget';
|
||||||
|
|
||||||
const RowWrapper = styled.div`
|
const RowWrapper = styled.div`
|
||||||
@@ -23,8 +24,12 @@ interface ListMapProps {
|
|||||||
dataList: any[];
|
dataList: any[];
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
btnText?: string;
|
btnText?: string;
|
||||||
|
requiredFields?: string[];
|
||||||
|
validateStatusList?: Record<string, statusType>[];
|
||||||
properties: Record<string, any>;
|
properties: Record<string, any>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
onAdd?: (data: any[]) => void;
|
||||||
|
onDelete?: (deletedItem: any, data: any[]) => void;
|
||||||
onChange?: (data: any) => void;
|
onChange?: (data: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +37,7 @@ interface ListItemProps {
|
|||||||
schemaList: any[];
|
schemaList: any[];
|
||||||
data: Record<string, any>;
|
data: Record<string, any>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
validateStatus?: Record<string, statusType>;
|
||||||
onChange?: (data: any) => void;
|
onChange?: (data: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +45,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||||||
schemaList,
|
schemaList,
|
||||||
data,
|
data,
|
||||||
onChange,
|
onChange,
|
||||||
|
validateStatus,
|
||||||
disabled
|
disabled
|
||||||
}) => {
|
}) => {
|
||||||
const handleValueChange = (name: string, target: any) => {
|
const handleValueChange = (name: string, target: any) => {
|
||||||
@@ -50,17 +57,18 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||||||
onChange?.({ [name]: value });
|
onChange?.({ [name]: value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{schemaList.map((schema: any) => (
|
{schemaList.map((schema: any) => (
|
||||||
<FormWidget
|
<FormWidget
|
||||||
|
status={validateStatus?.[schema.name]}
|
||||||
widget={schema.type}
|
widget={schema.type}
|
||||||
{...schema}
|
{...schema}
|
||||||
disabled={disabled}
|
disabled={disabled || schema.readOnly}
|
||||||
key={schema.name}
|
key={schema.name}
|
||||||
value={data?.[schema.name]}
|
value={data?.[schema.name]}
|
||||||
checked={data?.[schema.name]}
|
checked={data?.[schema.name]}
|
||||||
|
isInFormItems={false}
|
||||||
onChange={(target) => handleValueChange(schema.name, target)}
|
onChange={(target) => handleValueChange(schema.name, target)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -73,8 +81,12 @@ const ListMap: React.FC<ListMapProps> = ({
|
|||||||
label,
|
label,
|
||||||
btnText,
|
btnText,
|
||||||
properties = {},
|
properties = {},
|
||||||
|
requiredFields = [],
|
||||||
minItems = 0,
|
minItems = 0,
|
||||||
|
validateStatusList = [],
|
||||||
disabled,
|
disabled,
|
||||||
|
onAdd,
|
||||||
|
onDelete,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
const [items, setItems] = React.useState(dataList || []);
|
const [items, setItems] = React.useState(dataList || []);
|
||||||
@@ -82,23 +94,27 @@ const ListMap: React.FC<ListMapProps> = ({
|
|||||||
const schemaList = useMemo(() => {
|
const schemaList = useMemo(() => {
|
||||||
const list = Object.entries(properties).map(([key, value]) => ({
|
const list = Object.entries(properties).map(([key, value]) => ({
|
||||||
...value,
|
...value,
|
||||||
|
required: requiredFields.includes(key),
|
||||||
name: key
|
name: key
|
||||||
}));
|
}));
|
||||||
return list;
|
return list;
|
||||||
}, [properties]);
|
}, [properties, requiredFields]);
|
||||||
|
|
||||||
const handleOnAdd = () => {
|
const handleOnAdd = () => {
|
||||||
const keys = Object.keys(properties);
|
const keys = Object.keys(properties);
|
||||||
setItems([
|
const newItems = [
|
||||||
...items,
|
...items,
|
||||||
{ ...keys.reduce((acc, key) => ({ ...acc, [key]: '' }), {}) }
|
{ ...keys.reduce((acc, key) => ({ ...acc, [key]: '' }), {}) }
|
||||||
]);
|
];
|
||||||
|
setItems(newItems);
|
||||||
|
onAdd?.(newItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (index: number) => {
|
const handleDelete = (index: number) => {
|
||||||
|
const deleteItem = items[index];
|
||||||
const newItems = items.filter((_, i) => i !== index);
|
const newItems = items.filter((_, i) => i !== index);
|
||||||
setItems(newItems);
|
setItems(newItems);
|
||||||
onChange?.(newItems);
|
onDelete?.(deleteItem, newItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleItemChange = (index: number, data: { [key: string]: any }) => {
|
const handleItemChange = (index: number, data: { [key: string]: any }) => {
|
||||||
@@ -131,6 +147,7 @@ const ListMap: React.FC<ListMapProps> = ({
|
|||||||
<ListItem
|
<ListItem
|
||||||
schemaList={schemaList}
|
schemaList={schemaList}
|
||||||
data={item}
|
data={item}
|
||||||
|
validateStatus={validateStatusList?.[index]}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => handleItemChange(index, value)}
|
onChange={(value) => handleItemChange(index, value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ export interface FieldSchema {
|
|||||||
widget?: string;
|
widget?: string;
|
||||||
min?: number;
|
min?: number;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
required?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type statusType = 'error' | 'warning' | '' | undefined;
|
||||||
export interface FormWidgetProps {
|
export interface FormWidgetProps {
|
||||||
|
status?: statusType;
|
||||||
|
isInFormItems?: boolean;
|
||||||
widget: 'Input' | 'Select' | 'Checkbox' | 'InputNumber';
|
widget: 'Input' | 'Select' | 'Checkbox' | 'InputNumber';
|
||||||
name: string;
|
name: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
import { statusType } from '../config/types';
|
||||||
|
|
||||||
|
export default function useValidateFields(params: {
|
||||||
|
requiredFields?: string[];
|
||||||
|
setValidateStatusList: (statusList: { [key: string]: statusType }[]) => void;
|
||||||
|
}) {
|
||||||
|
const { requiredFields, setValidateStatusList } = params;
|
||||||
|
const validationEnabled = useRef(false);
|
||||||
|
|
||||||
|
const isEmptyValue = (value: any, key: string) => {
|
||||||
|
return !value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateRule = (value: any, key: string) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listMapValidator = async (_: any, valueList: any) => {
|
||||||
|
if (!validationEnabled.current) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = new Set<string>();
|
||||||
|
const statusList: { [key: string]: statusType }[] = [];
|
||||||
|
|
||||||
|
(valueList || []).forEach((item: any, index: number) => {
|
||||||
|
const status: { [key: string]: statusType } = {};
|
||||||
|
Object.entries(item || {}).forEach(([key, value]) => {
|
||||||
|
if (isEmptyValue(value, key)) {
|
||||||
|
fields.add(key);
|
||||||
|
if (requiredFields?.includes(key)) {
|
||||||
|
status[key] = 'error';
|
||||||
|
} else {
|
||||||
|
status[key] = '';
|
||||||
|
}
|
||||||
|
} else if (validateRule(value, key)) {
|
||||||
|
status[key] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
statusList.push(status);
|
||||||
|
});
|
||||||
|
|
||||||
|
setValidateStatusList(statusList);
|
||||||
|
|
||||||
|
if (fields.size > 0) {
|
||||||
|
return Promise.reject(`${Array.from(fields).join(', ')} is required`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleValidation = (enabled: boolean) => {
|
||||||
|
validationEnabled.current = enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listMapValidator,
|
||||||
|
toggleValidation
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@ import type { SelectProps } from 'antd';
|
|||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
|
|
||||||
const BaseSelect: React.FC<SelectProps> = forwardRef((props, ref) => {
|
const BaseSelect: React.FC<SelectProps & { ref?: any }> = forwardRef(
|
||||||
|
(props, ref) => {
|
||||||
const [isFocus, setIsFocus] = React.useState(false);
|
const [isFocus, setIsFocus] = React.useState(false);
|
||||||
const inputRef = React.useRef<any>(null);
|
const inputRef = React.useRef<any>(null);
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ const BaseSelect: React.FC<SelectProps> = forwardRef((props, ref) => {
|
|||||||
suffixIcon={renderSuffixIcon()}
|
suffixIcon={renderSuffixIcon()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default BaseSelect;
|
export default BaseSelect;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const SealInputNumber: React.FC<InputNumberProps & SealFormItemProps> = (
|
|||||||
if (isInFormItems) {
|
if (isInFormItems) {
|
||||||
const statusData = Form?.Item?.useStatus?.();
|
const statusData = Form?.Item?.useStatus?.();
|
||||||
status = statusData?.status || '';
|
status = statusData?.status || '';
|
||||||
|
} else {
|
||||||
|
status = props.status || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
|||||||
if (isInFormItems) {
|
if (isInFormItems) {
|
||||||
const statusData = Form?.Item?.useStatus?.();
|
const statusData = Form?.Item?.useStatus?.();
|
||||||
status = statusData?.status || '';
|
status = statusData?.status || '';
|
||||||
|
} else {
|
||||||
|
status = props.status || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ const SealSelect: React.FC<SelectProps & SealFormItemProps> = (props) => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [isFocus, setIsFocus] = useState(false);
|
const [isFocus, setIsFocus] = useState(false);
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
|
|
||||||
let status = '';
|
let status = '';
|
||||||
|
|
||||||
// the status can be controlled by Form.Item
|
// the status can be controlled by Form.Item
|
||||||
if (isInFormItems) {
|
if (isInFormItems) {
|
||||||
const statusData = Form?.Item?.useStatus?.();
|
const statusData = Form?.Item?.useStatus?.();
|
||||||
status = statusData?.status || '';
|
status = statusData?.status || '';
|
||||||
|
} else {
|
||||||
|
status = props.status || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const _options = useMemo(() => {
|
const _options = useMemo(() => {
|
||||||
|
|||||||
@@ -41,5 +41,7 @@ export default {
|
|||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
' On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
|
'cluster.create.checkEnv.tips':
|
||||||
|
'Use the following command to check if the environment is ready'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default {
|
|||||||
'menu.404': '404',
|
'menu.404': '404',
|
||||||
'menu.clusterManagement': 'Cluster Management',
|
'menu.clusterManagement': 'Cluster Management',
|
||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.clusterManagement.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Credentials',
|
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export default {
|
|||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
' On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
|
'cluster.create.checkEnv.tips':
|
||||||
|
'Use the following command to check if the environment is ready'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -84,6 +86,7 @@ export default {
|
|||||||
// 37. 'clusters.create.noRegions': 'No regions available',
|
// 37. 'clusters.create.noRegions': 'No regions available',
|
||||||
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool',
|
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool',
|
||||||
// 39. 'clusters.create.addworker.tips': ' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
// 39. 'clusters.create.addworker.tips': ' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
// 40. 'clusters.create.addCommand.tips': ' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
// 40. 'clusters.create.addCommand.tips': ' On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
|
// 41. 'cluster.create.checkEnv.tips': 'Use the following command to check if the environment is ready'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default {
|
|||||||
'menu.accessControl.users': 'Users',
|
'menu.accessControl.users': 'Users',
|
||||||
'menu.clusterManagement': 'Cluster Management',
|
'menu.clusterManagement': 'Cluster Management',
|
||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.clusterManagement.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Credentials',
|
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
||||||
'menu.models.userModels': 'My Models',
|
'menu.models.userModels': 'My Models',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
@@ -44,7 +44,7 @@ export default {
|
|||||||
// 7. 'menu.accessControl.users': 'Users',
|
// 7. 'menu.accessControl.users': 'Users',
|
||||||
// 8. 'menu.clusterManagement': 'Cluster Management',
|
// 8. 'menu.clusterManagement': 'Cluster Management',
|
||||||
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
||||||
// 10. 'menu.clusterManagement.credentials': 'Credentials',
|
// 10. 'menu.clusterManagement.credentials': 'Cloud Credentials',
|
||||||
// 11. 'menu.models.userModels': 'My Models'
|
// 11. 'menu.models.userModels': 'My Models'
|
||||||
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export default {
|
|||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
' On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
|
'cluster.create.checkEnv.tips':
|
||||||
|
'Use the following command to check if the environment is ready'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -84,6 +86,7 @@ export default {
|
|||||||
// 37. 'clusters.create.noRegions': 'No regions available',
|
// 37. 'clusters.create.noRegions': 'No regions available',
|
||||||
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool'
|
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool'
|
||||||
// 39. 'clusters.create.addworker.tips': ' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
// 39. 'clusters.create.addworker.tips': ' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
// 40. 'clusters.create.addCommand.tips': ' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
// 40. 'clusters.create.addCommand.tips': ' On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
|
// 41. 'cluster.create.checkEnv.tips':'Use the following command to check if the environment is ready'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default {
|
|||||||
'menu.accessControl.users': 'Пользователи',
|
'menu.accessControl.users': 'Пользователи',
|
||||||
'menu.clusterManagement': 'Cluster Management',
|
'menu.clusterManagement': 'Cluster Management',
|
||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.clusterManagement.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Credentials',
|
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
||||||
'menu.models.userModels': 'My Models',
|
'menu.models.userModels': 'My Models',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
@@ -44,7 +44,7 @@ export default {
|
|||||||
// 7. 'menu.accessControl.users': 'Users',
|
// 7. 'menu.accessControl.users': 'Users',
|
||||||
// 8. 'menu.clusterManagement': 'Cluster Management',
|
// 8. 'menu.clusterManagement': 'Cluster Management',
|
||||||
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
// 9. 'menu.clusterManagement.clusters': 'Clusters',
|
||||||
// 10. 'menu.clusterManagement.credentials': 'Credentials',
|
// 10. 'menu.clusterManagement.credentials': 'Cloud Credentials',
|
||||||
// 11. 'menu.models.userModels': 'My Models',
|
// 11. 'menu.models.userModels': 'My Models',
|
||||||
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
||||||
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster'
|
||||||
|
|||||||
@@ -40,5 +40,6 @@ export default {
|
|||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
'在执行以下命令之前,请确保已满足 <a href={link} target="_blank">{label}</a> 的先决条件。',
|
'在执行以下命令之前,请确保已满足 <a href={link} target="_blank">{label}</a> 的先决条件。',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
' 在需要添加的 Worker 上运行以下命令,将其加入到集群中'
|
' 在需要添加的 Worker 上运行以下命令,将其加入到集群中',
|
||||||
|
'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default {
|
|||||||
'menu.accessControl.users': '用户',
|
'menu.accessControl.users': '用户',
|
||||||
'menu.clusterManagement': '集群管理',
|
'menu.clusterManagement': '集群管理',
|
||||||
'menu.clusterManagement.clusters': '集群',
|
'menu.clusterManagement.clusters': '集群',
|
||||||
'menu.clusterManagement.credentials': '凭证',
|
'menu.clusterManagement.credentials': '云凭证',
|
||||||
'menu.clusterManagement.clusterDetail': '集群详情',
|
'menu.clusterManagement.clusterDetail': '集群详情',
|
||||||
'menu.clusterManagement.clusterCreate': '创建集群'
|
'menu.clusterManagement.clusterCreate': '创建集群'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ProviderType, ProviderValueMap } from '../config';
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
import AddWorkerCommand from './add-worker-command';
|
import AddWorkerCommand from './add-worker-command';
|
||||||
|
import CheckEnvCommand from './check-env-command';
|
||||||
import RegisterClusterInner from './register-cluster-inner';
|
import RegisterClusterInner from './register-cluster-inner';
|
||||||
import SupportedGPUs from './support-gpus';
|
import SupportedGPUs from './support-gpus';
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
|
|||||||
registrationInfo
|
registrationInfo
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [currentSelection, setCurrentSelection] =
|
const [currentGPU, setCurrentGPU] = React.useState<string>('cuda');
|
||||||
React.useState<string>('cuda');
|
React.useState<string>('cuda');
|
||||||
const [workerCommand, setWorkerCommand] = React.useState<Record<string, any>>(
|
const [workerCommand, setWorkerCommand] = React.useState<Record<string, any>>(
|
||||||
{
|
{
|
||||||
@@ -73,7 +74,7 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
|
|||||||
|
|
||||||
const handleSelectProvider = (value: string, item: any) => {
|
const handleSelectProvider = (value: string, item: any) => {
|
||||||
if (provider !== ProviderValueMap.Docker) return;
|
if (provider !== ProviderValueMap.Docker) return;
|
||||||
setCurrentSelection(value);
|
setCurrentGPU(value);
|
||||||
setWorkerCommand(item);
|
setWorkerCommand(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,10 +85,10 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
|
|||||||
</Title>
|
</Title>
|
||||||
<SupportedGPUs
|
<SupportedGPUs
|
||||||
onSelect={handleSelectProvider}
|
onSelect={handleSelectProvider}
|
||||||
current={provider === ProviderValueMap.Docker ? currentSelection : ''}
|
current={currentGPU}
|
||||||
clickable={provider === ProviderValueMap.Docker}
|
clickable={true}
|
||||||
/>
|
/>
|
||||||
{provider === ProviderValueMap.Docker && (
|
|
||||||
<Content>
|
<Content>
|
||||||
<Line></Line>
|
<Line></Line>
|
||||||
<Alert
|
<Alert
|
||||||
@@ -106,9 +107,13 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
|
|||||||
}
|
}
|
||||||
></Alert>
|
></Alert>
|
||||||
</Content>
|
</Content>
|
||||||
)}
|
|
||||||
<div className="command-info">
|
<div className="command-info">
|
||||||
{intl.formatMessage({ id: 'clusters.create.addCommand.tips' })}
|
1. {intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })}
|
||||||
|
</div>
|
||||||
|
<CheckEnvCommand provider={provider} currentGPU={currentGPU} />
|
||||||
|
<div className="command-info">
|
||||||
|
2. {intl.formatMessage({ id: 'clusters.create.addCommand.tips' })}
|
||||||
</div>
|
</div>
|
||||||
{provider === ProviderValueMap.Kubernetes ? (
|
{provider === ProviderValueMap.Kubernetes ? (
|
||||||
<RegisterClusterInner registrationInfo={registrationInfo} />
|
<RegisterClusterInner registrationInfo={registrationInfo} />
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import HighlightCode from '@/components/highlight-code';
|
||||||
|
import { addWorkerGuide } from '@/pages/resources/config';
|
||||||
|
import React from 'react';
|
||||||
|
import { ProviderType } from '../config';
|
||||||
|
|
||||||
|
type ViewModalProps = {
|
||||||
|
provider: ProviderType;
|
||||||
|
currentGPU: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddWorkerCommand: React.FC<ViewModalProps> = ({
|
||||||
|
provider = '',
|
||||||
|
currentGPU
|
||||||
|
}) => {
|
||||||
|
const code = React.useMemo(() => {
|
||||||
|
const command = addWorkerGuide['cuda'];
|
||||||
|
return command.checkEnvCommand[provider || ''];
|
||||||
|
}, [provider]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HighlightCode
|
||||||
|
theme="dark"
|
||||||
|
code={code}
|
||||||
|
copyValue={code}
|
||||||
|
lang="bash"
|
||||||
|
></HighlightCode>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddWorkerCommand;
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
|
import GaugeChart from '@/components/echarts/gauge';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import StatusTag from '@/components/status-tag';
|
||||||
|
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||||
|
import Card from '@/components/templates/card';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
|
import { Card as ACard, Col, Collapse, Row } from 'antd';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { queryClusterToken } from '../apis';
|
||||||
|
import {
|
||||||
|
ClusterStatus,
|
||||||
|
ClusterStatusLabelMap,
|
||||||
|
ProviderLabelMap,
|
||||||
|
ProviderValueMap,
|
||||||
|
clusterActionList
|
||||||
|
} from '../config';
|
||||||
|
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
|
||||||
|
import AddPool from './add-pool';
|
||||||
|
import RegisterCluster from './register-cluster';
|
||||||
|
import WorkerPools from './worker-pools';
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.chart-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardWrapper = styled(ACard)`
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: none !important;
|
||||||
|
flex: 1;
|
||||||
|
.ant-card {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.ant-card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
font-size: var(--font-size-large);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardBox = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Inner = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CollapseWrapper = styled(Collapse)`
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
|
background-color: var(--ant-color-fill-quaternary);
|
||||||
|
.ant-collapse-content {
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
|
}
|
||||||
|
.ant-collapse-header {
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
padding-block: 10px !important;
|
||||||
|
.ant-collapse-expand-icon {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table .ant-table-thead tr {
|
||||||
|
background-color: transparent !important;
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid var(--ant-color-split) !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SubTitle = styled.div`
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
margin-block: 24px 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface CardProps {
|
||||||
|
data: ListItem;
|
||||||
|
onSelect?: (key: string, row: ListItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gaugeConfig = {
|
||||||
|
radius: '100%',
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
roundCap: false,
|
||||||
|
width: 8
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 8,
|
||||||
|
color: [
|
||||||
|
[0.5, 'rgba(84, 204, 152, 80%)'],
|
||||||
|
[0.8, 'rgba(250, 173, 20, 80%)'],
|
||||||
|
[1, 'rgba(255, 77, 79, 80%)']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardItem: React.FC<CardProps> = (props) => {
|
||||||
|
const chartHeight = 160;
|
||||||
|
const { data, onSelect } = props;
|
||||||
|
const [show, setShow] = React.useState(false);
|
||||||
|
const [addPoolStatus, setAddPoolStatus] = React.useState({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
provider: ProviderValueMap.DigitalOcean
|
||||||
|
});
|
||||||
|
const [registerClusterStatus, setRegisterClusterStatus] = React.useState<{
|
||||||
|
open: boolean;
|
||||||
|
registrationInfo: {
|
||||||
|
token: string;
|
||||||
|
image: string;
|
||||||
|
server_url: string;
|
||||||
|
cluster_id: number;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
registrationInfo: {
|
||||||
|
token: '',
|
||||||
|
image: '',
|
||||||
|
server_url: '',
|
||||||
|
cluster_id: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRegisterCluster = async () => {
|
||||||
|
try {
|
||||||
|
const info = await queryClusterToken({ id: data.id });
|
||||||
|
setRegisterClusterStatus({
|
||||||
|
open: true,
|
||||||
|
registrationInfo: {
|
||||||
|
...info,
|
||||||
|
cluster_id: data.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// cluster action handler
|
||||||
|
const handleOnSelect = (key: string) => {
|
||||||
|
if (key === 'addPool') {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: true,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: 'Add Worker Pool',
|
||||||
|
provider: data.provider
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'register_cluster') {
|
||||||
|
handleRegisterCluster();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSelect?.(key, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// pool action handler
|
||||||
|
const handleOnAction = (action: string, record: NodePoolListItem) => {
|
||||||
|
if (action === 'edit') {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: true,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: 'Edit Worker Pool',
|
||||||
|
provider: data.provider
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = useMemo(() => {
|
||||||
|
return clusterActionList.filter((item) => {
|
||||||
|
if (item.provider) {
|
||||||
|
return item.provider === data.provider;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [data.provider]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
height={'auto'}
|
||||||
|
clickable={false}
|
||||||
|
ghost
|
||||||
|
footerHolder={
|
||||||
|
<CollapseWrapper
|
||||||
|
onChange={() => setShow(!show)}
|
||||||
|
expandIconPosition="end"
|
||||||
|
expandIcon={({ isActive }) => (
|
||||||
|
<IconFont type="icon-down" rotate={isActive ? 0 : -90} />
|
||||||
|
)}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'More Information',
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<div className="chart-wrapper">
|
||||||
|
<Row gutter={16} style={{ width: '100%' }}>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="GPU Utilization"
|
||||||
|
value={85}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="CPU Utilization"
|
||||||
|
value={50}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="RAM Utilization"
|
||||||
|
value={70}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="VRAM Utilization"
|
||||||
|
value={60}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
{data.provider === ProviderValueMap.DigitalOcean && (
|
||||||
|
<>
|
||||||
|
<SubTitle>Worker Pools</SubTitle>
|
||||||
|
<WorkerPools
|
||||||
|
provider={data.provider}
|
||||||
|
workerPools={data.worker_pools}
|
||||||
|
height={show ? 'auto' : 0}
|
||||||
|
onAction={handleOnAction}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></CollapseWrapper>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Inner>
|
||||||
|
<div className="title">
|
||||||
|
<span className="flex-center gap-8">
|
||||||
|
<span className="text">{data.name}</span>
|
||||||
|
<ThemeTag color="purple">
|
||||||
|
{ProviderLabelMap[data.provider]}
|
||||||
|
</ThemeTag>
|
||||||
|
<StatusTag
|
||||||
|
statusValue={{
|
||||||
|
status: ClusterStatus[data.state],
|
||||||
|
text: ClusterStatusLabelMap[data.state]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<DropdownButtons
|
||||||
|
items={actions}
|
||||||
|
onSelect={handleOnSelect}
|
||||||
|
></DropdownButtons>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Content>
|
||||||
|
<CardBox>
|
||||||
|
<CardWrapper bordered={false}>
|
||||||
|
<div className="label">Workers</div>
|
||||||
|
<div className="value">
|
||||||
|
{data.ready_workers} / {data.workers}
|
||||||
|
</div>
|
||||||
|
</CardWrapper>
|
||||||
|
<CardWrapper bordered={false}>
|
||||||
|
<div className="label">GPUs</div>
|
||||||
|
<div className="value">{data.gpus}</div>
|
||||||
|
</CardWrapper>
|
||||||
|
<CardWrapper bordered={false}>
|
||||||
|
<div className="label">Deployments</div>
|
||||||
|
<div className="value">{data.models}</div>
|
||||||
|
</CardWrapper>
|
||||||
|
</CardBox>
|
||||||
|
</Content>
|
||||||
|
</Inner>
|
||||||
|
<AddPool
|
||||||
|
provider={addPoolStatus.provider}
|
||||||
|
open={addPoolStatus.open}
|
||||||
|
action={addPoolStatus.action}
|
||||||
|
title={addPoolStatus.title}
|
||||||
|
onCancel={() => {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onOk={() => {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: false,
|
||||||
|
action: addPoolStatus.action,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></AddPool>
|
||||||
|
<RegisterCluster
|
||||||
|
title="Register Cluster"
|
||||||
|
open={registerClusterStatus.open}
|
||||||
|
registrationInfo={registerClusterStatus.registrationInfo}
|
||||||
|
onCancel={() => {
|
||||||
|
setRegisterClusterStatus({
|
||||||
|
open: false,
|
||||||
|
registrationInfo: {
|
||||||
|
token: '',
|
||||||
|
image: '',
|
||||||
|
server_url: '',
|
||||||
|
cluster_id: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></RegisterCluster>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardItem;
|
||||||
@@ -205,8 +205,6 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
value: string | number;
|
value: string | number;
|
||||||
}) => {
|
}) => {
|
||||||
if (action === PageAction.EDIT) {
|
if (action === PageAction.EDIT) {
|
||||||
console.log('imageLabelRender========::', action, currentData, data);
|
|
||||||
// return currentData?.image_name || currentData?.os_image;
|
|
||||||
const vendor = _.split(currentData?.image_name || '', ' ')[0];
|
const vendor = _.split(currentData?.image_name || '', ' ')[0];
|
||||||
const iconType = _.get(vendorIconMap, vendor.toLowerCase());
|
const iconType = _.get(vendorIconMap, vendor.toLowerCase());
|
||||||
return (
|
return (
|
||||||
@@ -217,7 +215,6 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const selectImage = osImageList.find((item) => item.value === data.value);
|
const selectImage = osImageList.find((item) => item.value === data.value);
|
||||||
console.log('imageLabelRender========::', selectImage);
|
|
||||||
if (selectImage) {
|
if (selectImage) {
|
||||||
return (
|
return (
|
||||||
<RenderLabel label={data.label} vendor={selectImage.vendor || ''} />
|
<RenderLabel label={data.label} vendor={selectImage.vendor || ''} />
|
||||||
@@ -272,14 +269,6 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterImageOption = (inputValue: string, option: any) => {
|
|
||||||
return (
|
|
||||||
option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
|
|
||||||
option.value.toLowerCase().includes(inputValue.toLowerCase()) ||
|
|
||||||
option.description.toLowerCase().includes(inputValue.toLowerCase())
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
resetFields: () => {
|
resetFields: () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import ListMap from '@/components/dynamic-form/components/list-map';
|
import ListMap from '@/components/dynamic-form/components/list-map';
|
||||||
|
import { statusType } from '@/components/dynamic-form/config/types';
|
||||||
|
import useValidateFields from '@/components/dynamic-form/hooks/use-validate-fields';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef, useState } from 'react';
|
||||||
import { fieldConfig } from '../config/cloud-options-config';
|
import { fieldConfig } from '../config/cloud-options-config';
|
||||||
|
|
||||||
const volumeOptions = fieldConfig.volumes;
|
const volumeOptions = fieldConfig.volumes;
|
||||||
@@ -14,21 +16,56 @@ const CloudOptions: React.FC<{
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const volumes = Form.useWatch(['cloud_options', volumeOptions.name], form);
|
const volumes = Form.useWatch(['cloud_options', volumeOptions.name], form);
|
||||||
|
const [validateStatusList, setValidateStatusList] = useState<
|
||||||
|
{ [key: string]: statusType }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const handleOnChange = (name: string, value: any) => {
|
const updateValidateStatusList = (list: { [key: string]: statusType }[]) => {
|
||||||
console.log('handleOnChange========', name, value);
|
setValidateStatusList(list);
|
||||||
form.setFieldValue(['cloud_options', name], value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { listMapValidator, toggleValidation } = useValidateFields({
|
||||||
|
requiredFields: volumeOptions.required,
|
||||||
|
setValidateStatusList: updateValidateStatusList
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOnChange = (value: any) => {
|
||||||
|
toggleValidation(true);
|
||||||
|
form.setFieldValue(['cloud_options', volumeOptions.name], value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnAdd = (data: any[]) => {
|
||||||
|
toggleValidation(false);
|
||||||
|
form.setFieldValue(['cloud_options', volumeOptions.name], data);
|
||||||
|
toggleValidation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnDelete = (deletedItem: any, data: any[]) => {
|
||||||
|
toggleValidation(false);
|
||||||
|
form.setFieldValue(['cloud_options', volumeOptions.name], data);
|
||||||
|
toggleValidation(true);
|
||||||
|
};
|
||||||
|
console.log('disabled=====', disabled);
|
||||||
return (
|
return (
|
||||||
<Form.Item name={['cloud_options', volumeOptions.name as string]}>
|
<Form.Item
|
||||||
|
name={['cloud_options', volumeOptions.name as string]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: listMapValidator
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
<ListMap
|
<ListMap
|
||||||
|
validateStatusList={validateStatusList}
|
||||||
btnText={intl.formatMessage({ id: 'clusters.workerpool.volumes.add' })}
|
btnText={intl.formatMessage({ id: 'clusters.workerpool.volumes.add' })}
|
||||||
label={intl.formatMessage({ id: 'clusters.workerpool.volumes' })}
|
label={intl.formatMessage({ id: 'clusters.workerpool.volumes' })}
|
||||||
dataList={volumes || []}
|
dataList={volumes || []}
|
||||||
properties={volumeOptions.properties || {}}
|
properties={volumeOptions.properties || {}}
|
||||||
|
requiredFields={volumeOptions.required || []}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => handleOnChange(volumeOptions.name, value)}
|
onAdd={handleOnAdd}
|
||||||
|
onDelete={handleOnDelete}
|
||||||
|
onChange={handleOnChange}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const fieldConfig: Record<string, FieldSchema> = {
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
title: 'Volumes',
|
title: 'Volumes',
|
||||||
name: 'volumes',
|
name: 'volumes',
|
||||||
|
required: ['name', 'size_gb', 'format'],
|
||||||
properties: {
|
properties: {
|
||||||
name: {
|
name: {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
|
|||||||
@@ -351,6 +351,9 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
}, [onCancel]);
|
}, [onCancel]);
|
||||||
|
|
||||||
const initClusterId = () => {
|
const initClusterId = () => {
|
||||||
|
if (initialValues?.cluster_id) {
|
||||||
|
return initialValues.cluster_id;
|
||||||
|
}
|
||||||
const cluster_id =
|
const cluster_id =
|
||||||
clusterList?.find((item) => item.provider === ProviderValueMap.Docker)
|
clusterList?.find((item) => item.provider === ProviderValueMap.Docker)
|
||||||
?.value || clusterList?.[0]?.value;
|
?.value || clusterList?.[0]?.value;
|
||||||
@@ -361,14 +364,12 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
const handleOnOpen = () => {
|
const handleOnOpen = () => {
|
||||||
if (props.deploymentType === 'modelFiles') {
|
if (props.deploymentType === 'modelFiles') {
|
||||||
form.current?.form?.setFieldsValue({
|
form.current?.form?.setFieldsValue({
|
||||||
...props.initialValues,
|
...props.initialValues
|
||||||
cluster_id: initClusterId()
|
|
||||||
});
|
});
|
||||||
handleOnValuesChange?.({
|
handleOnValuesChange?.({
|
||||||
changedValues: {},
|
changedValues: {},
|
||||||
allValues: {
|
allValues: {
|
||||||
...props.initialValues,
|
...props.initialValues
|
||||||
cluster_id: initClusterId()
|
|
||||||
},
|
},
|
||||||
source: source
|
source: source
|
||||||
});
|
});
|
||||||
@@ -422,7 +423,7 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
message: []
|
message: []
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}, [open, clusterList]);
|
}, [open, clusterList, initialValues?.cluster_id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GSDrawer
|
<GSDrawer
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const useGenerateWorkerOptions = () => {
|
|||||||
const [workersList, setWorkersList] = useState<
|
const [workersList, setWorkersList] = useState<
|
||||||
Global.BaseOption<
|
Global.BaseOption<
|
||||||
number,
|
number,
|
||||||
{ state: string; labels: Record<string, string> }
|
{ state: string; labels: Record<string, string>; cluster_id: number }
|
||||||
>[]
|
>[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
@@ -129,7 +129,11 @@ export const useGenerateWorkerOptions = () => {
|
|||||||
value: cluster.id,
|
value: cluster.id,
|
||||||
parent: true,
|
parent: true,
|
||||||
children: workerList
|
children: workerList
|
||||||
.filter((worker) => worker.cluster_id === cluster.id)
|
.filter(
|
||||||
|
(worker) =>
|
||||||
|
worker.cluster_id === cluster.id &&
|
||||||
|
worker.state === WorkerStatusMap.ready
|
||||||
|
)
|
||||||
.map((worker) => ({
|
.map((worker) => ({
|
||||||
disabled: WorkerStatusMap.ready !== worker.state,
|
disabled: WorkerStatusMap.ready !== worker.state,
|
||||||
state: worker.state,
|
state: worker.state,
|
||||||
@@ -166,6 +170,7 @@ export const useGenerateWorkerOptions = () => {
|
|||||||
generateCascaderWorkerOptions(workerList, clusterList);
|
generateCascaderWorkerOptions(workerList, clusterList);
|
||||||
setWorkersList(
|
setWorkersList(
|
||||||
workerList.map((item) => ({
|
workerList.map((item) => ({
|
||||||
|
cluster_id: item.cluster_id,
|
||||||
state: item.state,
|
state: item.state,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id
|
value: item.id
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ const ModelFiles = () => {
|
|||||||
?.labels?.['worker-name'];
|
?.labels?.['worker-name'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
cluster_id: workersList?.find(
|
||||||
|
(worker) => worker.value === record.worker_id
|
||||||
|
)?.cluster_id,
|
||||||
source: modelSourceMap.local_path_value,
|
source: modelSourceMap.local_path_value,
|
||||||
local_path: record.resolved_paths?.[0],
|
local_path: record.resolved_paths?.[0],
|
||||||
worker_selector: targetWorker
|
worker_selector: targetWorker
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { StatusMaps } from '@/config';
|
import { StatusMaps } from '@/config';
|
||||||
|
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||||
|
|
||||||
export const WorkerStatusMap = {
|
export const WorkerStatusMap = {
|
||||||
ready: 'ready',
|
ready: 'ready',
|
||||||
@@ -57,16 +58,18 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
image: string;
|
image: string;
|
||||||
workerip: string;
|
workerip: string;
|
||||||
}) {
|
}) {
|
||||||
return `docker run -d --name gpustack \\
|
return `docker run -d --name gpustack-worker \\
|
||||||
--restart=unless-stopped \\
|
--restart=unless-stopped \\
|
||||||
--gpus all \\
|
--privileged \\
|
||||||
--network=host \\
|
--net=host \\
|
||||||
--ipc=host \\
|
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
${params.image} \\
|
|
||||||
--server-url ${params.server} \\
|
--server-url ${params.server} \\
|
||||||
--registration-token ${params.token} \\
|
--registration-token ${params.token} \\
|
||||||
--worker-ip ${params.workerip}`;
|
--worker-ip ${params.workerip}`;
|
||||||
|
},
|
||||||
|
checkEnvCommand: {
|
||||||
|
[ProviderValueMap.Docker]: `nvidia-smi >/dev/null 2>&1 && echo "NVIDIA driver OK" || (echo "NVIDIA driver issue"; exit 1) && docker info 2>/dev/null | grep -q "Default Runtime: nvidia" && echo "NVIDIA Container Toolkit OK" || (echo "NVIDIA Container Toolkit not configured"; exit 1)`,
|
||||||
|
[ProviderValueMap.Kubernetes]: 'k8s env check command'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
npu: {
|
npu: {
|
||||||
|
|||||||
Reference in New Issue
Block a user