chore: worker selector
This commit is contained in:
@@ -0,0 +1,138 @@
|
|||||||
|
import AutoComplete from '@/components/seal-form/auto-complete';
|
||||||
|
import { MinusOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { useLabelSelectorContext } from './context';
|
||||||
|
import './styles/label-item.less';
|
||||||
|
interface LabelItemProps {
|
||||||
|
label: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
labels?: Record<string, any>;
|
||||||
|
labelKey?: string;
|
||||||
|
labelValue?: string;
|
||||||
|
keyAddon?: React.ReactNode;
|
||||||
|
valueAddon?: React.ReactNode;
|
||||||
|
seperator?: string;
|
||||||
|
labelList: { key: string; value: string }[];
|
||||||
|
disabled?: boolean;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onChange?: (params: { key: string; value: string }) => void;
|
||||||
|
onPaste?: (e: any) => void;
|
||||||
|
onBlur?: (e: any, type: string) => void;
|
||||||
|
}
|
||||||
|
const LabelItem: React.FC<LabelItemProps> = ({
|
||||||
|
labels,
|
||||||
|
label,
|
||||||
|
labelList,
|
||||||
|
seperator,
|
||||||
|
keyAddon,
|
||||||
|
valueAddon,
|
||||||
|
disabled,
|
||||||
|
onChange,
|
||||||
|
onDelete,
|
||||||
|
onBlur
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const { options } = useLabelSelectorContext();
|
||||||
|
|
||||||
|
const keyOptions = useMemo(() => {
|
||||||
|
return options?.filter(
|
||||||
|
(item) => !_.has(labels, item.value) || item.value === label.key
|
||||||
|
);
|
||||||
|
}, [labels, options]);
|
||||||
|
|
||||||
|
const valueOptions = useMemo(() => {
|
||||||
|
return options?.find((item) => item.value === label.key)?.children || [];
|
||||||
|
}, [label.key, options]);
|
||||||
|
|
||||||
|
const handleOnValueChange = (value: string) => {
|
||||||
|
onChange?.({
|
||||||
|
key: label.key,
|
||||||
|
value: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnKeyChange = (key: any) => {
|
||||||
|
onChange?.({
|
||||||
|
key,
|
||||||
|
value: label.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyOnBlur = (e: any, type: string) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
// has duplicate key
|
||||||
|
const duplicates = _.filter(
|
||||||
|
labelList,
|
||||||
|
(item: Global.BaseListItem<string>) => val && val === item.key
|
||||||
|
);
|
||||||
|
if (duplicates.length > 1) {
|
||||||
|
setOpen(true);
|
||||||
|
onChange?.({
|
||||||
|
key: '',
|
||||||
|
value: label.value
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpen(false);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
onBlur?.(e, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="label-item">
|
||||||
|
<div className="label-key">
|
||||||
|
{keyAddon ?? (
|
||||||
|
<Tooltip
|
||||||
|
open={open}
|
||||||
|
title={intl.formatMessage({ id: 'resources.table.key.tips' })}
|
||||||
|
>
|
||||||
|
<AutoComplete
|
||||||
|
options={keyOptions}
|
||||||
|
disabled={disabled}
|
||||||
|
checkStatus="success"
|
||||||
|
label={intl.formatMessage({ id: 'common.input.key' })}
|
||||||
|
value={label.key}
|
||||||
|
onChange={handleOnKeyChange}
|
||||||
|
onBlur={(e: any) => handleKeyOnBlur(e, 'key')}
|
||||||
|
></AutoComplete>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{seperator && <span className="seprator">{seperator}</span>}
|
||||||
|
<div className="label-value">
|
||||||
|
{valueAddon ?? (
|
||||||
|
<AutoComplete
|
||||||
|
options={valueOptions}
|
||||||
|
disabled={disabled}
|
||||||
|
checkStatus={label.value ? 'success' : ''}
|
||||||
|
label={intl.formatMessage({ id: 'common.input.value' })}
|
||||||
|
value={label.value}
|
||||||
|
onChange={handleOnValueChange}
|
||||||
|
onBlur={(e: any) => onBlur?.(e, 'value')}
|
||||||
|
></AutoComplete>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!disabled && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
className="btn"
|
||||||
|
type="default"
|
||||||
|
shape="circle"
|
||||||
|
onClick={onDelete}
|
||||||
|
>
|
||||||
|
<MinusOutlined />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LabelItem;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface LabelSelectorContextProps {
|
||||||
|
options?: Array<{
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
children?: { label: string; value: string | number }[];
|
||||||
|
}>;
|
||||||
|
currentData?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LabelSelectorContext =
|
||||||
|
React.createContext<LabelSelectorContextProps>(
|
||||||
|
{} as LabelSelectorContextProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useLabelSelectorContext = () => {
|
||||||
|
const context = React.useContext(LabelSelectorContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useLabelSelectorContext must be used within a LabelSelectorProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ interface LabelSelectorProps {
|
|||||||
btnText?: string;
|
btnText?: string;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
isAutoComplete?: boolean;
|
||||||
onChange?: (labels: Record<string, any>) => void;
|
onChange?: (labels: Record<string, any>) => void;
|
||||||
onBlur?: (e: any, type: string, index: number) => void;
|
onBlur?: (e: any, type: string, index: number) => void;
|
||||||
onDelete?: (index: number) => void;
|
onDelete?: (index: number) => void;
|
||||||
@@ -22,7 +23,8 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
|
|||||||
disabled,
|
disabled,
|
||||||
label,
|
label,
|
||||||
btnText,
|
btnText,
|
||||||
description
|
description,
|
||||||
|
isAutoComplete
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [labelsData, setLabelsData] = useState({});
|
const [labelsData, setLabelsData] = useState({});
|
||||||
@@ -89,6 +91,7 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
|
|||||||
description={
|
description={
|
||||||
description ?? intl.formatMessage({ id: 'models.form.keyvalue.paste' })
|
description ?? intl.formatMessage({ id: 'models.form.keyvalue.paste' })
|
||||||
}
|
}
|
||||||
|
isAutoComplete={isAutoComplete}
|
||||||
labels={labelsData}
|
labels={labelsData}
|
||||||
labelList={labelList}
|
labelList={labelList}
|
||||||
onChange={handleLabelsChange}
|
onChange={handleLabelsChange}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import AutoCompleteItem from './autocomplete-item';
|
||||||
import LabelItem from './label-item';
|
import LabelItem from './label-item';
|
||||||
import Wrapper from './wrapper';
|
import Wrapper from './wrapper';
|
||||||
interface LabelSelectorProps {
|
interface LabelSelectorProps {
|
||||||
labels: Record<string, any>;
|
labels: Record<string, any>;
|
||||||
label?: string;
|
label?: string;
|
||||||
btnText?: string;
|
btnText?: string;
|
||||||
|
isAutoComplete?: boolean;
|
||||||
labelList: Array<{ key: string; value: string }>;
|
labelList: Array<{ key: string; value: string }>;
|
||||||
onLabelListChange: (list: { key: string; value: string }[]) => void;
|
onLabelListChange: (list: { key: string; value: string }[]) => void;
|
||||||
onChange?: (labels: Record<string, any>) => void;
|
onChange?: (labels: Record<string, any>) => void;
|
||||||
@@ -28,10 +30,15 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
|||||||
disabled,
|
disabled,
|
||||||
label,
|
label,
|
||||||
btnText,
|
btnText,
|
||||||
description
|
description,
|
||||||
|
isAutoComplete
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('labels changed in Inner', labels);
|
||||||
|
}, [labels]);
|
||||||
|
|
||||||
const updateLabels = (list: { key: string; value: string }[]) => {
|
const updateLabels = (list: { key: string; value: string }[]) => {
|
||||||
const newLabels = _.reduce(
|
const newLabels = _.reduce(
|
||||||
list,
|
list,
|
||||||
@@ -81,21 +88,38 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
|||||||
btnText={btnText}
|
btnText={btnText}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{labelList?.map((item: any, index: number) => {
|
{isAutoComplete
|
||||||
return (
|
? labelList?.map((item: any, index: number) => {
|
||||||
<LabelItem
|
return (
|
||||||
disabled={disabled}
|
<AutoCompleteItem
|
||||||
key={index}
|
disabled={disabled}
|
||||||
label={item}
|
key={index}
|
||||||
seperator=":"
|
label={item}
|
||||||
labelList={labelList}
|
seperator=":"
|
||||||
onDelete={() => handleOnDelete(index)}
|
labels={labels}
|
||||||
onChange={(obj) => handleOnChange(index, obj)}
|
labelList={labelList}
|
||||||
onPaste={(e) => onPaste?.(e, index)}
|
onDelete={() => handleOnDelete(index)}
|
||||||
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
onChange={(obj) => handleOnChange(index, obj)}
|
||||||
/>
|
onPaste={(e) => onPaste?.(e, index)}
|
||||||
);
|
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
||||||
})}
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: labelList?.map((item: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<LabelItem
|
||||||
|
disabled={disabled}
|
||||||
|
key={index}
|
||||||
|
label={item}
|
||||||
|
seperator=":"
|
||||||
|
labelList={labelList}
|
||||||
|
onDelete={() => handleOnDelete(index)}
|
||||||
|
onChange={(obj) => handleOnChange(index, obj)}
|
||||||
|
onPaste={(e) => onPaste?.(e, index)}
|
||||||
|
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const SealAutoComplete: React.FC<
|
|||||||
trim = true,
|
trim = true,
|
||||||
onSelect,
|
onSelect,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
checkStatus,
|
||||||
extra,
|
extra,
|
||||||
style,
|
style,
|
||||||
addAfter,
|
addAfter,
|
||||||
@@ -83,7 +84,7 @@ const SealAutoComplete: React.FC<
|
|||||||
<SelectWrapper style={style}>
|
<SelectWrapper style={style}>
|
||||||
<Wrapper
|
<Wrapper
|
||||||
className="seal-select-wrapper"
|
className="seal-select-wrapper"
|
||||||
status={status}
|
status={checkStatus || status}
|
||||||
extra={extra}
|
extra={extra}
|
||||||
label={label}
|
label={label}
|
||||||
isFocus={isFocus}
|
isFocus={isFocus}
|
||||||
@@ -103,6 +104,8 @@ const SealAutoComplete: React.FC<
|
|||||||
''
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
status={checkStatus || status}
|
||||||
onSelect={handleOnSelect}
|
onSelect={handleOnSelect}
|
||||||
onFocus={handleOnFocus}
|
onFocus={handleOnFocus}
|
||||||
onBlur={handleOnBlur}
|
onBlur={handleOnBlur}
|
||||||
|
|||||||
@@ -2,12 +2,24 @@ import { PageActionType } from '@/config/types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BackendOption, DeployFormKey } from './types';
|
import { BackendOption, DeployFormKey } from './types';
|
||||||
|
|
||||||
|
type EmptyObject = Record<never, never>;
|
||||||
|
|
||||||
|
type CascaderOption<T extends object = EmptyObject> = {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
parent?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
index?: number;
|
||||||
|
children?: CascaderOption<T>[];
|
||||||
|
} & Partial<T>;
|
||||||
|
|
||||||
interface FormContextProps {
|
interface FormContextProps {
|
||||||
isGGUF?: boolean;
|
isGGUF?: boolean;
|
||||||
formKey: DeployFormKey;
|
formKey: DeployFormKey;
|
||||||
source: string;
|
source: string;
|
||||||
pageAction: PageActionType;
|
pageAction: PageActionType;
|
||||||
gpuOptions: any[];
|
gpuOptions: CascaderOption[];
|
||||||
|
workerLabelOptions: CascaderOption[];
|
||||||
backendOptions: BackendOption[];
|
backendOptions: BackendOption[];
|
||||||
onValuesChange?: (changedValues: any, allValues: any) => void;
|
onValuesChange?: (changedValues: any, allValues: any) => void;
|
||||||
onBackendChange: (backend: string, option: any) => void;
|
onBackendChange: (backend: string, option: any) => void;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import LabelSelector from '@/components/label-selector';
|
import LabelSelector from '@/components/label-selector';
|
||||||
|
import { LabelSelectorContext } from '@/components/label-selector/context';
|
||||||
import CheckboxField from '@/components/seal-form/checkbox-field';
|
import CheckboxField from '@/components/seal-form/checkbox-field';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import TooltipList from '@/components/tooltip-list';
|
import TooltipList from '@/components/tooltip-list';
|
||||||
@@ -36,7 +37,7 @@ const AdvanceConfig = () => {
|
|||||||
const EnviromentVars = Form.useWatch('env', form);
|
const EnviromentVars = Form.useWatch('env', form);
|
||||||
const scheduleType = Form.useWatch('scheduleType', form);
|
const scheduleType = Form.useWatch('scheduleType', form);
|
||||||
const backend = Form.useWatch('backend', form);
|
const backend = Form.useWatch('backend', form);
|
||||||
const { onValuesChange } = useFormContext();
|
const { onValuesChange, workerLabelOptions } = useFormContext();
|
||||||
|
|
||||||
const handleWorkerLabelsChange = useCallback(
|
const handleWorkerLabelsChange = useCallback(
|
||||||
(labels: Record<string, any>) => {
|
(labels: Record<string, any>) => {
|
||||||
@@ -104,52 +105,57 @@ const AdvanceConfig = () => {
|
|||||||
}
|
}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<FormData>
|
<LabelSelectorContext.Provider
|
||||||
name="worker_selector"
|
value={{ options: workerLabelOptions }}
|
||||||
rules={[
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (
|
|
||||||
getFieldValue('scheduleType') === ScheduleValueMap.Auto &&
|
|
||||||
_.keys(value).length > 0
|
|
||||||
) {
|
|
||||||
if (_.some(_.keys(value), (k: string) => !value[k])) {
|
|
||||||
return Promise.reject(
|
|
||||||
intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: 'common.validate.value'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: intl.formatMessage({
|
|
||||||
id: 'models.form.selector'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<LabelSelector
|
<Form.Item<FormData>
|
||||||
label={intl.formatMessage({
|
name="worker_selector"
|
||||||
id: 'resources.form.workerSelector'
|
rules={[
|
||||||
})}
|
({ getFieldValue }) => ({
|
||||||
labels={wokerSelector}
|
validator(rule, value) {
|
||||||
onChange={handleWorkerLabelsChange}
|
if (
|
||||||
onBlur={handleSelectorOnBlur}
|
getFieldValue('scheduleType') === ScheduleValueMap.Auto &&
|
||||||
onDelete={handleDeleteWorkerSelector}
|
_.keys(value).length > 0
|
||||||
description={
|
) {
|
||||||
<span>
|
if (_.some(_.keys(value), (k: string) => !value[k])) {
|
||||||
{intl.formatMessage({
|
return Promise.reject(
|
||||||
id: 'resources.form.workerSelector.description'
|
intl.formatMessage(
|
||||||
})}
|
{
|
||||||
</span>
|
id: 'common.validate.value'
|
||||||
}
|
},
|
||||||
></LabelSelector>
|
{
|
||||||
</Form.Item>
|
name: intl.formatMessage({
|
||||||
|
id: 'models.form.selector'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<LabelSelector
|
||||||
|
isAutoComplete
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: 'resources.form.workerSelector'
|
||||||
|
})}
|
||||||
|
labels={wokerSelector}
|
||||||
|
onChange={handleWorkerLabelsChange}
|
||||||
|
onBlur={handleSelectorOnBlur}
|
||||||
|
onDelete={handleDeleteWorkerSelector}
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'resources.form.workerSelector.description'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
></LabelSelector>
|
||||||
|
</Form.Item>
|
||||||
|
</LabelSelectorContext.Provider>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
onOk
|
onOk
|
||||||
} = props;
|
} = props;
|
||||||
const { backendOptions, getBackendOptions } = useQueryBackends();
|
const { backendOptions, getBackendOptions } = useQueryBackends();
|
||||||
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
|
const { getGPUOptionList, gpuOptions, workerLabelOptions } =
|
||||||
|
useGenerateGPUOptions();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [activeKey, setActiveKey] = React.useState<string[]>([]);
|
const [activeKey, setActiveKey] = React.useState<string[]>([]);
|
||||||
@@ -208,6 +209,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
pageAction: action,
|
pageAction: action,
|
||||||
gpuOptions: gpuOptions,
|
gpuOptions: gpuOptions,
|
||||||
backendOptions: backendOptions,
|
backendOptions: backendOptions,
|
||||||
|
workerLabelOptions: workerLabelOptions,
|
||||||
onValuesChange: onValuesChange,
|
onValuesChange: onValuesChange,
|
||||||
onBackendChange: handleBackendChange
|
onBackendChange: handleBackendChange
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ type CascaderOption<T extends object = EmptyObject> = {
|
|||||||
|
|
||||||
export const useGenerateGPUOptions = () => {
|
export const useGenerateGPUOptions = () => {
|
||||||
const [gpuOptions, setGpuOptions] = useState<CascaderOption[]>([]);
|
const [gpuOptions, setGpuOptions] = useState<CascaderOption[]>([]);
|
||||||
|
const [workerLabelOptions, setWorkerLabelOptions] = useState<
|
||||||
|
CascaderOption[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const generateCascaderGPUOptions = (
|
const generateCascaderGPUOptions = (
|
||||||
gpuList: GPUListItem[],
|
gpuList: GPUListItem[],
|
||||||
@@ -80,7 +83,34 @@ export const useGenerateGPUOptions = () => {
|
|||||||
return gpuSelectorList;
|
return gpuSelectorList;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateWorkerSelectorOptions = (workerList: WorkerListItem[]) => {};
|
const generateWorkerSelectorOptions = (workerList: WorkerListItem[]) => {
|
||||||
|
// each worker may have multiple labels,the labels is object as: {key: value, key2: value2}
|
||||||
|
// different workers may have a same label key but different values
|
||||||
|
// we need to extract a list as: [{label: key, value: key, children: [{label: value, value: value}]}]
|
||||||
|
const labelMap = new Map<string, Set<string>>();
|
||||||
|
workerList.forEach((worker) => {
|
||||||
|
const labels = worker.labels || {};
|
||||||
|
Object.entries(labels).forEach(([key, value]) => {
|
||||||
|
if (!labelMap.has(key)) {
|
||||||
|
labelMap.set(key, new Set());
|
||||||
|
}
|
||||||
|
labelMap.get(key)!.add(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelOptions: CascaderOption[] = Array.from(labelMap.entries()).map(
|
||||||
|
([key, values]) => ({
|
||||||
|
label: key,
|
||||||
|
value: key,
|
||||||
|
parent: true,
|
||||||
|
children: Array.from(values).map((value) => ({
|
||||||
|
label: value,
|
||||||
|
value: value
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return labelOptions;
|
||||||
|
};
|
||||||
|
|
||||||
const getGPUOptionList = async (params?: {
|
const getGPUOptionList = async (params?: {
|
||||||
clusterId: number;
|
clusterId: number;
|
||||||
@@ -99,13 +129,16 @@ export const useGenerateGPUOptions = () => {
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
const gpuList = generateCascaderGPUOptions(gpuData.items, workerData.items);
|
const gpuList = generateCascaderGPUOptions(gpuData.items, workerData.items);
|
||||||
|
const labelOptions = generateWorkerSelectorOptions(workerData.items);
|
||||||
setGpuOptions(gpuList);
|
setGpuOptions(gpuList);
|
||||||
|
setWorkerLabelOptions(labelOptions);
|
||||||
return gpuList;
|
return gpuList;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getGPUOptionList,
|
getGPUOptionList,
|
||||||
gpuOptions
|
gpuOptions,
|
||||||
|
workerLabelOptions
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ export const requestConfig: RequestConfig = {
|
|||||||
},
|
},
|
||||||
errorHandler: (error: any, opts: any) => {
|
errorHandler: (error: any, opts: any) => {
|
||||||
const { message: errorMessage, response } = error;
|
const { message: errorMessage, response } = error;
|
||||||
const errMsg = response?.data?.message || errorMessage;
|
const errMsg =
|
||||||
|
response?.data?.error?.message ||
|
||||||
|
response?.data?.message ||
|
||||||
|
errorMessage;
|
||||||
|
|
||||||
if (!opts?.skipErrorHandler && response?.status) {
|
if (!opts?.skipErrorHandler && response?.status) {
|
||||||
message.error(errMsg);
|
message.error(errMsg);
|
||||||
|
|||||||
Reference in New Issue
Block a user