|
| 1 | +import LabelSelector from '@/components/label-selector'; |
| 2 | +import { LabelSelectorContext } from '@/components/label-selector/context'; |
1 | 3 | import SealInputNumber from '@/components/seal-form/input-number'; |
2 | 4 | import SealCascader from '@/components/seal-form/seal-cascader'; |
3 | 5 | import SealSelect from '@/components/seal-form/seal-select'; |
4 | 6 | import TooltipList from '@/components/tooltip-list'; |
5 | 7 | import useAppUtils from '@/hooks/use-app-utils'; |
6 | 8 | import { useIntl } from '@umijs/max'; |
7 | 9 | import { Form } from 'antd'; |
| 10 | +import _ from 'lodash'; |
8 | 11 | import React from 'react'; |
9 | 12 | import GPUCard from '../components/gpu-card'; |
10 | | -import { gpusCountTypeMap, scheduleList, ScheduleValueMap } from '../config'; |
| 13 | +import { |
| 14 | + gpusCountTypeMap, |
| 15 | + placementStrategyOptions, |
| 16 | + scheduleList, |
| 17 | + ScheduleValueMap |
| 18 | +} from '../config'; |
11 | 19 | import { backendOptionsMap } from '../config/backend-parameters'; |
12 | 20 | import { useFormContext } from '../config/form-context'; |
| 21 | +import { FormData } from '../config/types'; |
| 22 | + |
| 23 | +const placementStrategyTips = [ |
| 24 | + { |
| 25 | + title: 'Spread', |
| 26 | + tips: 'resources.form.spread.tips' |
| 27 | + }, |
| 28 | + { |
| 29 | + title: 'Binpack', |
| 30 | + tips: 'resources.form.binpack.tips' |
| 31 | + } |
| 32 | +]; |
13 | 33 |
|
14 | 34 | const scheduleTypeTips = [ |
15 | 35 | { |
@@ -47,11 +67,12 @@ const gpuAllocateTypeTips = [ |
47 | 67 |
|
48 | 68 | const ScheduleTypeForm: React.FC = () => { |
49 | 69 | const intl = useIntl(); |
50 | | - const { onValuesChange, gpuOptions } = useFormContext(); |
| 70 | + const { onValuesChange, gpuOptions, workerLabelOptions } = useFormContext(); |
51 | 71 | const { getRuleMessage } = useAppUtils(); |
52 | 72 | const form = Form.useFormInstance(); |
53 | 73 | const scheduleType = Form.useWatch('scheduleType', form); |
54 | 74 | const gpusCountType = Form.useWatch('gpusCountType', form); |
| 75 | + const wokerSelector = Form.useWatch('worker_selector', form); |
55 | 76 |
|
56 | 77 | const handleScheduleTypeChange = (value: string) => { |
57 | 78 | if (value === ScheduleValueMap.Auto) { |
@@ -105,9 +126,32 @@ const ScheduleTypeForm: React.FC = () => { |
105 | 126 | } |
106 | 127 | }; |
107 | 128 |
|
| 129 | + const onSelectorChange = (field: string, allowEmpty?: boolean) => { |
| 130 | + const workerSelector = form.getFieldValue(field); |
| 131 | + // check if all keys have values |
| 132 | + const hasEmptyValue = _.some(_.keys(workerSelector), (k: string) => { |
| 133 | + return !workerSelector[k]; |
| 134 | + }); |
| 135 | + if (!hasEmptyValue || allowEmpty) { |
| 136 | + onValuesChange?.({}, form.getFieldsValue()); |
| 137 | + } |
| 138 | + }; |
| 139 | + |
| 140 | + const handleWorkerLabelsChange = (labels: Record<string, any>) => { |
| 141 | + form.setFieldValue('worker_selector', labels); |
| 142 | + }; |
| 143 | + |
| 144 | + const handleSelectorOnBlur = () => { |
| 145 | + onSelectorChange('worker_selector'); |
| 146 | + }; |
| 147 | + |
| 148 | + const handleDeleteWorkerSelector = (index: number) => { |
| 149 | + onValuesChange?.({}, form.getFieldsValue()); |
| 150 | + }; |
| 151 | + |
108 | 152 | return ( |
109 | 153 | <> |
110 | | - <Form.Item name="scheduleType"> |
| 154 | + <Form.Item name="scheduleType" data-field="scheduleType"> |
111 | 155 | <SealSelect |
112 | 156 | onChange={handleScheduleTypeChange} |
113 | 157 | label={intl.formatMessage({ id: 'models.form.scheduletype' })} |
@@ -208,6 +252,72 @@ const ScheduleTypeForm: React.FC = () => { |
208 | 252 | )} |
209 | 253 | </> |
210 | 254 | )} |
| 255 | + {scheduleType === ScheduleValueMap.Auto && ( |
| 256 | + <> |
| 257 | + <Form.Item<FormData> name="placement_strategy"> |
| 258 | + <SealSelect |
| 259 | + label={intl.formatMessage({ |
| 260 | + id: 'resources.form.placementStrategy' |
| 261 | + })} |
| 262 | + options={placementStrategyOptions} |
| 263 | + description={ |
| 264 | + <TooltipList list={placementStrategyTips}></TooltipList> |
| 265 | + } |
| 266 | + ></SealSelect> |
| 267 | + </Form.Item> |
| 268 | + <LabelSelectorContext.Provider |
| 269 | + value={{ options: workerLabelOptions }} |
| 270 | + > |
| 271 | + <Form.Item<FormData> |
| 272 | + name="worker_selector" |
| 273 | + rules={[ |
| 274 | + ({ getFieldValue }) => ({ |
| 275 | + validator(rule, value) { |
| 276 | + if ( |
| 277 | + scheduleType === ScheduleValueMap.Auto && |
| 278 | + _.keys(value).length > 0 |
| 279 | + ) { |
| 280 | + if (_.some(_.keys(value), (k: string) => !value[k])) { |
| 281 | + return Promise.reject( |
| 282 | + intl.formatMessage( |
| 283 | + { |
| 284 | + id: 'common.validate.value' |
| 285 | + }, |
| 286 | + { |
| 287 | + name: intl.formatMessage({ |
| 288 | + id: 'models.form.selector' |
| 289 | + }) |
| 290 | + } |
| 291 | + ) |
| 292 | + ); |
| 293 | + } |
| 294 | + } |
| 295 | + return Promise.resolve(); |
| 296 | + } |
| 297 | + }) |
| 298 | + ]} |
| 299 | + > |
| 300 | + <LabelSelector |
| 301 | + isAutoComplete |
| 302 | + label={intl.formatMessage({ |
| 303 | + id: 'resources.form.workerSelector' |
| 304 | + })} |
| 305 | + labels={wokerSelector} |
| 306 | + onChange={handleWorkerLabelsChange} |
| 307 | + onBlur={handleSelectorOnBlur} |
| 308 | + onDelete={handleDeleteWorkerSelector} |
| 309 | + description={ |
| 310 | + <span> |
| 311 | + {intl.formatMessage({ |
| 312 | + id: 'resources.form.workerSelector.description' |
| 313 | + })} |
| 314 | + </span> |
| 315 | + } |
| 316 | + ></LabelSelector> |
| 317 | + </Form.Item> |
| 318 | + </LabelSelectorContext.Provider> |
| 319 | + </> |
| 320 | + )} |
211 | 321 | </> |
212 | 322 | ); |
213 | 323 | }; |
|
0 commit comments