fix: add worker, create cluster
This commit is contained in:
@@ -45,6 +45,7 @@ export function useUpdateChunkedList(options: {
|
||||
}, 200);
|
||||
};
|
||||
const updateChunkedList = (data: ChunkedCollection) => {
|
||||
console.log('updateChunkedList data:', data);
|
||||
let collections = data?.collection || [];
|
||||
if (options?.computedID) {
|
||||
collections = collections?.map((item: any) => {
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
CredentialFormData,
|
||||
CredentialListItem,
|
||||
NodePoolFormData,
|
||||
NodePoolListItem
|
||||
NodePoolListItem,
|
||||
SystemConfig
|
||||
} from '../config/types';
|
||||
|
||||
export const CREDENTIALS_API = '/cloud-credentials';
|
||||
@@ -19,6 +20,8 @@ export const CLUSTER_TOKEN = 'registration-token';
|
||||
|
||||
export const PROVIDER_PROXY_API = '/provider-proxy';
|
||||
|
||||
export const SYSTEM_CONFIG_API = '/config';
|
||||
|
||||
// ============= DigitalOcean start =====================
|
||||
|
||||
export const REGIONS_API = '/v2/regions';
|
||||
@@ -189,3 +192,9 @@ export async function deleteWorkerPool(id: number) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
|
||||
export async function querySystemConfig() {
|
||||
return request<SystemConfig>(`${SYSTEM_CONFIG_API}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ const ClusterCreate = () => {
|
||||
const breadcrumbItems = [
|
||||
{
|
||||
title: <a>{intl.formatMessage({ id: 'clusters.title' })}</a>,
|
||||
onClick: () => navigate(-1)
|
||||
onClick: () => handleCancel()
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.button.create' })
|
||||
|
||||
@@ -2,6 +2,7 @@ import AlertInfoBlock from '@/components/alert-info/block';
|
||||
import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message';
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Alert } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ProviderType, ProviderValueMap } from '../../config';
|
||||
@@ -29,6 +30,7 @@ const Container = styled.div`
|
||||
* clusterList and onClusterChange are only required when from worker page.
|
||||
*/
|
||||
type AddWorkerProps = {
|
||||
isModal?: boolean;
|
||||
provider: ProviderType;
|
||||
clusterList?: Global.BaseOption<number, ClusterListItem>[];
|
||||
clusterLoading?: boolean;
|
||||
@@ -49,6 +51,7 @@ type AddWorkerProps = {
|
||||
*/
|
||||
const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
||||
const {
|
||||
isModal = false,
|
||||
registrationInfo,
|
||||
provider,
|
||||
clusterList,
|
||||
@@ -63,7 +66,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
||||
);
|
||||
const startWatchRef = React.useRef(false);
|
||||
const { update, summary, register } = useSummaryStatus();
|
||||
const { contextHolder, createModelsChunkRequest } = useAddWorkerMessage({
|
||||
const { addedCount, createModelsChunkRequest } = useAddWorkerMessage({
|
||||
startWatch: startWatchRef
|
||||
});
|
||||
|
||||
@@ -84,8 +87,10 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
||||
}, [stepList]);
|
||||
|
||||
React.useEffect(() => {
|
||||
createModelsChunkRequest();
|
||||
}, []);
|
||||
if (!isModal) {
|
||||
createModelsChunkRequest();
|
||||
}
|
||||
}, [isModal]);
|
||||
|
||||
return (
|
||||
<AddWorkerContext.Provider
|
||||
@@ -139,8 +144,18 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{addedCount > 0 && (
|
||||
<Alert
|
||||
message={intl.formatMessage(
|
||||
{
|
||||
id: 'clusters.addworker.message.success'
|
||||
},
|
||||
{ count: addedCount }
|
||||
)}
|
||||
type="warning"
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
{contextHolder}
|
||||
</AddWorkerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
import ScrollerModal from '@/components/scroller-modal';
|
||||
import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Alert, Button } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { queryClusterToken } from '../../apis';
|
||||
import { ProviderType } from '../../config';
|
||||
import { ClusterListItem } from '../../config/types';
|
||||
import AddWorkerStep from './add-worker-step';
|
||||
import { StepName } from './config';
|
||||
|
||||
const Footer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
`;
|
||||
|
||||
type AddWorkerProps = {
|
||||
open: boolean;
|
||||
provider: ProviderType;
|
||||
@@ -40,6 +51,11 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
||||
clusterLoading,
|
||||
stepList = []
|
||||
} = props || {};
|
||||
const intl = useIntl();
|
||||
const startWatchRef = React.useRef(false);
|
||||
const { addedCount, createModelsChunkRequest } = useAddWorkerMessage({
|
||||
startWatch: startWatchRef
|
||||
});
|
||||
const firstLoad = React.useRef(true);
|
||||
const [registrationInfo, setRegistrationInfo] = React.useState<{
|
||||
token: string;
|
||||
@@ -61,6 +77,7 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
||||
...data,
|
||||
cluster_id: value
|
||||
});
|
||||
startWatchRef.current = true;
|
||||
} catch (error) {
|
||||
firstLoad.current = false;
|
||||
}
|
||||
@@ -75,6 +92,12 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
||||
};
|
||||
}, [open, cluster_id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
createModelsChunkRequest();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<ScrollerModal
|
||||
title={title}
|
||||
@@ -88,9 +111,35 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
||||
width={865}
|
||||
style={{}}
|
||||
maxContentHeight={'max(calc(100vh - 200px), 600px)'}
|
||||
footer={null}
|
||||
footer={
|
||||
<Footer>
|
||||
<span>
|
||||
{addedCount > 0 && (
|
||||
<Alert
|
||||
message={intl.formatMessage(
|
||||
{
|
||||
id: 'clusters.addworker.message.success'
|
||||
},
|
||||
{ count: addedCount }
|
||||
)}
|
||||
type="warning"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onCancel}
|
||||
style={{
|
||||
minWidth: 88
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.button.done' })}
|
||||
</Button>
|
||||
</Footer>
|
||||
}
|
||||
>
|
||||
<AddWorkerStep
|
||||
isModal={true}
|
||||
stepList={stepList}
|
||||
provider={provider}
|
||||
clusterList={clusterList}
|
||||
|
||||
@@ -134,7 +134,7 @@ const SummaryData: React.FC = () => {
|
||||
tips={
|
||||
workerIPConfig.enable
|
||||
? workerIPConfig.ip
|
||||
? workerIPConfig.ip
|
||||
? ''
|
||||
: intl.formatMessage({ id: 'clusters.addworker.notSpecified' })
|
||||
: intl.formatMessage({ id: 'clusters.addworker.autoDetect' })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fromClusterCreationAtom } from '@/atoms/clusters';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
@@ -11,6 +12,7 @@ import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ClusterFormData as FormData } from '../config/types';
|
||||
import { useProviderRegions } from '../hooks/use-provider-regions';
|
||||
import useSystemConfig from '../services/use-system-config';
|
||||
|
||||
const OptionItem = styled.div`
|
||||
display: flex;
|
||||
@@ -74,6 +76,8 @@ const optionRender = (option: any): React.ReactNode => {
|
||||
const CloudProvider: React.FC<CloudProviderProps> = (props) => {
|
||||
const { credentialList, action, credentialID } = props;
|
||||
const intl = useIntl();
|
||||
const { systemConfig } = useSystemConfig();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
|
||||
const {
|
||||
getRegions,
|
||||
@@ -105,6 +109,10 @@ const CloudProvider: React.FC<CloudProviderProps> = (props) => {
|
||||
}
|
||||
}, [credentialID]);
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldValue('server_url', systemConfig?.server_external_url || '');
|
||||
}, [systemConfig]);
|
||||
|
||||
const labelRender = (props: {
|
||||
label: React.ReactNode;
|
||||
value: string | number;
|
||||
@@ -176,6 +184,21 @@ const CloudProvider: React.FC<CloudProviderProps> = (props) => {
|
||||
})}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="server_url"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', 'clusters.create.serverUrl')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'clusters.create.serverUrl' })}
|
||||
required={true}
|
||||
trim={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,9 +40,7 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
||||
form.setFieldsValue(currentData);
|
||||
|
||||
if (advanceConfigRef.current) {
|
||||
const workerConfigYaml = json2Yaml({
|
||||
worker_config: currentData.worker_config || {}
|
||||
});
|
||||
const workerConfigYaml = json2Yaml(currentData.worker_config || {});
|
||||
advanceConfigRef.current?.setYamlValue(workerConfigYaml);
|
||||
}
|
||||
}
|
||||
@@ -69,7 +67,9 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
||||
|
||||
return {
|
||||
...values,
|
||||
...workerConfig
|
||||
worker_config: {
|
||||
...workerConfig
|
||||
}
|
||||
};
|
||||
}
|
||||
}));
|
||||
@@ -129,6 +129,7 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
|
||||
children: (
|
||||
<AdvanceConfig
|
||||
action={action}
|
||||
provider={provider}
|
||||
ref={advanceConfigRef}
|
||||
></AdvanceConfig>
|
||||
)
|
||||
|
||||
@@ -79,3 +79,9 @@ export interface ClusterFormData {
|
||||
worker_config?: Record<string, any>;
|
||||
worker_pools?: NodePoolFormData[];
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
debug: boolean;
|
||||
server_external_url: string;
|
||||
system_default_container_registry: string;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"gateway_mode": {
|
||||
"type": "string",
|
||||
"description": "Gateway mode",
|
||||
"enum": ["worker", "gateway", "disabled"]
|
||||
"enum": ["worker", "gateway", "disabled", "auto"]
|
||||
},
|
||||
"gateway_kubeconfig": {
|
||||
"type": "string",
|
||||
@@ -88,9 +88,7 @@
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"description": "Additional resource definitions",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"pipx_path": {
|
||||
"type": "string",
|
||||
|
||||
@@ -1,53 +1,54 @@
|
||||
export default `# This is a template for worker_config.
|
||||
|
||||
worker_config:
|
||||
debug: true
|
||||
# debug: false
|
||||
|
||||
# directories
|
||||
cache_dir:
|
||||
log_dir:
|
||||
bin_dir:
|
||||
# ========= directories ===========
|
||||
|
||||
# container & image
|
||||
system_default_container_registry:
|
||||
image_repo: gpustack/worker
|
||||
image_name_override: ""
|
||||
# cache_dir: "/var/lib/gpustack/cache"
|
||||
# log_dir: "/var/lib/gpustack/log"
|
||||
# bin_dir: "/var/lib/gpustack/bin"
|
||||
|
||||
# gateway
|
||||
gateway_mode:
|
||||
gateway_concurrency: 0
|
||||
gateway_kubeconfig: ""
|
||||
# ========= container & image ===========
|
||||
|
||||
# service & networking
|
||||
service_discovery_name:
|
||||
namespace: default
|
||||
worker_port:
|
||||
worker_metrics_port:
|
||||
service_port_range:
|
||||
ray_port_range:
|
||||
# system_default_container_registry: "docker.io"
|
||||
# image_name_override: "gpustack/gpustack:main"
|
||||
# image_repo: "gpustack/gpustack"
|
||||
|
||||
# resources
|
||||
resources:
|
||||
gpu:
|
||||
count:
|
||||
type:
|
||||
cpu:
|
||||
limit:
|
||||
memory:
|
||||
limit:
|
||||
# ========= gateway ===========
|
||||
|
||||
# huggingface
|
||||
huggingface_token: ""
|
||||
enable_hf_transfer: true
|
||||
enable_hf_xet: true
|
||||
# gateway_mode: "auto"
|
||||
# gateway_concurrency: 16
|
||||
# gateway_kubeconfig: "/var/lib/gpustack/higress/kubeconfig"
|
||||
|
||||
# metrics
|
||||
disable_worker_metrics: false
|
||||
# ========= service & networking ===========
|
||||
|
||||
# tools & runtime
|
||||
pipx_path:
|
||||
tools_download_base_url:
|
||||
# service_discovery_name: "worker"
|
||||
# namespace: "gpustack-system"
|
||||
# worker_port: 10150
|
||||
# worker_metrics_port: 10150
|
||||
# service_port_range: "40000-40063"
|
||||
# ray_port_range: "41000-41999"
|
||||
|
||||
# proxy
|
||||
proxy_mode: worker
|
||||
# ========= resources ===========
|
||||
|
||||
# resources:
|
||||
|
||||
# ========= huggingface ===========
|
||||
|
||||
# huggingface_token:
|
||||
# enable_hf_transfer: false
|
||||
# enable_hf_xet: false
|
||||
|
||||
# ========= metrics ===========
|
||||
|
||||
# disable_worker_metrics: false
|
||||
|
||||
# ========= tools & runtime ===========
|
||||
|
||||
# pipx_path: "/usr/local/bin/pipx"
|
||||
# tools_download_base_url:
|
||||
|
||||
# ========= proxy ===========
|
||||
|
||||
# proxy_mode: worker
|
||||
`;
|
||||
|
||||
@@ -25,6 +25,7 @@ export default function useAddWorkerMessage(params: {
|
||||
onCreate: (newItems: any) => {
|
||||
if (params.startWatch?.current) {
|
||||
newItemsRef.current = newItemsRef.current.concat(newItems);
|
||||
console.log('newItemsRef.current:', newItemsRef.current);
|
||||
showAddWorkerMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { querySystemConfig } from '../apis';
|
||||
import { SystemConfig } from '../config/types';
|
||||
|
||||
export default function useSystemConfig() {
|
||||
const [systemConfig, setSystemConfig] = useState<SystemConfig>(
|
||||
{} as SystemConfig
|
||||
);
|
||||
|
||||
const fetchSystemConfig = async () => {
|
||||
try {
|
||||
const data = await querySystemConfig();
|
||||
setSystemConfig(data);
|
||||
} catch (error) {
|
||||
// handle error if needed
|
||||
setSystemConfig({} as SystemConfig);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSystemConfig();
|
||||
}, []);
|
||||
|
||||
return { systemConfig };
|
||||
}
|
||||
@@ -4,29 +4,34 @@ import YamlEditor from '@/pages/_components/yaml-editor';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import { ProviderType, ProviderValueMap } from '../config';
|
||||
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>('');
|
||||
const ClusterAdvanceConfig: React.FC<{
|
||||
action: PageActionType;
|
||||
provider: ProviderType;
|
||||
ref?: any;
|
||||
}> = forwardRef(({ action, provider }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
const intl = useIntl();
|
||||
const editorRef = React.useRef<any>(null);
|
||||
const [fileContent, setFileContent] = React.useState<string>(yamlTemplate);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getYamlValue: () => {
|
||||
return editorRef.current?.getValue();
|
||||
},
|
||||
setYamlValue: (values: any) => {
|
||||
console.log('setYamlValue:', values, editorRef.current);
|
||||
editorRef.current?.setValue(values);
|
||||
}
|
||||
}));
|
||||
useImperativeHandle(ref, () => ({
|
||||
getYamlValue: () => {
|
||||
return editorRef.current?.getValue();
|
||||
},
|
||||
setYamlValue: (values: any) => {
|
||||
console.log('setYamlValue:', values, editorRef.current);
|
||||
editorRef.current?.setValue(values);
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
return (
|
||||
<>
|
||||
{provider !== ProviderValueMap.DigitalOcean && (
|
||||
<Form.Item<FormData>
|
||||
name="server_url"
|
||||
rules={[
|
||||
@@ -42,34 +47,31 @@ const ClusterAdvanceConfig: React.FC<{ action: PageActionType; ref?: any }> =
|
||||
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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
)}
|
||||
<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}
|
||||
></YamlEditor>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default ClusterAdvanceConfig;
|
||||
|
||||
Reference in New Issue
Block a user