Browse Source

feat: 封装云际组件

pull/274/head
zhaowei 6 months ago
parent
commit
2692d54b2d
6 changed files with 354 additions and 43 deletions
  1. +50
    -10
      react-ui/src/components/ParameterSelect/config.tsx
  2. +43
    -26
      react-ui/src/components/ParameterSelect/index.tsx
  3. +44
    -7
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  4. +87
    -0
      react-ui/src/services/external/index.ts
  5. +128
    -0
      react-ui/src/state/jcdResource.ts
  6. +2
    -0
      react-ui/src/utils/sessionStorage.ts

+ 50
- 10
react-ui/src/components/ParameterSelect/config.tsx View File

@@ -3,6 +3,7 @@ import { DatasetData, ModelData } from '@/pages/Dataset/config';
import { ServiceData } from '@/pages/ModelDeployment/types';
import { getDatasetList, getModelList } from '@/services/dataset/index.js';
import { getServiceListReq } from '@/services/modelDeployment';
import type { JCCResourceImage, JCCResourceStandard, JCCResourceType } from '@/state/jcdResource';
import { type SelectProps } from 'antd';

export type SelectPropsConfig = {
@@ -10,12 +11,21 @@ export type SelectPropsConfig = {
fieldNames?: SelectProps['fieldNames']; // 下拉数据字段
optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名
filterOption?: SelectProps['filterOption']; // 过滤函数
getValue: (value: any) => string | number;
getLabel: (value: any) => string;
isObjectValue: boolean; // value 是对象
getValue?: (value: any) => string | number; // 对象类型时,获取其值
getLabel?: (value: any) => string; // 对象类型时,获取其 label
};

export const paramSelectConfig: Record<string, SelectPropsConfig> = {
export type ParameterSelectDataType =
| 'dataset'
| 'model'
| 'service'
| 'resource'
| 'remote-image'
| 'remote-resource-type'
| 'remote-resource';

export const paramSelectConfig: Record<ParameterSelectDataType, SelectPropsConfig> = {
dataset: {
getOptions: async () => {
const res = await getDatasetList({
@@ -72,14 +82,44 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = {
resource: {
fieldNames: resourceFieldNames,
filterOption: filterResourceStandard as SelectProps['filterOption'],
// 不会用到
getValue: () => {
return '';
isObjectValue: false,
},
'remote-resource-type': {
optionFilterProp: 'label',
isObjectValue: false,
getValue: (value: JCCResourceType) => {
return value.value;
},
// 不会用的
getLabel: () => {
return '';
getLabel: (value: JCCResourceType) => {
return value.label;
},
isObjectValue: false,
},
'remote-image': {
optionFilterProp: 'label',
getValue: (value: JCCResourceImage) => {
return value.imageID;
},
getLabel: (value: JCCResourceImage) => {
return value.name;
},
isObjectValue: true,
},
'remote-resource': {
optionFilterProp: 'label',
getValue: (value: JCCResourceStandard) => {
return value.id;
},
getLabel: (value: JCCResourceStandard) => {
const cpu = value.baseResourceSpecs.find((v) => v.type === 'CPU');
const ram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'RAM');
const vram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'VRAM');
const cpuText = cpu ? `CPU:${cpu.availableValue}, ` : '';
const ramText = ram ? `内存: ${ram.availableValue}${ram.availableUnit?.toUpperCase()}` : '';
const vramText = vram
? `(显存${vram.availableValue}${vram.availableUnit?.toUpperCase()})`
: '';
return `${value.type}: ${value.availableCount}*${value.name}${vramText}, ${cpuText}${ramText}`;
},
isObjectValue: true,
},
};

+ 43
- 26
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -5,18 +5,22 @@
*/

import { useComputingResource } from '@/hooks/useComputingResource';
import state from '@/state/jcdResource';
import { to } from '@/utils/promise';
import { useSnapshot } from '@umijs/max';
import { Select, type SelectProps } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import FormInfo from '../FormInfo';
import { paramSelectConfig } from './config';
import { paramSelectConfig, type ParameterSelectDataType } from './config';

export { type ParameterSelectDataType };

export type ParameterSelectObject = {
value: any;
[key: string]: any;
};

export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource';
const identityFunc = (value: any) => value;

export interface ParameterSelectProps extends SelectProps {
/** 类型 */
@@ -25,8 +29,6 @@ export interface ParameterSelectProps extends SelectProps {
display?: boolean;
/** 值,支持对象,对象必须包含 value */
value?: string | ParameterSelectObject;
/** 用于流水线, 流水线资源规格要求 id 为字符串 */
isPipeline?: boolean;
/** 修改后回调 */
onChange?: (value: string | ParameterSelectObject) => void;
}
@@ -36,15 +38,14 @@ function ParameterSelect({
dataType,
display = false,
value,
// isPipeline = false,
onChange,
...rest
}: ParameterSelectProps) {
const [options, setOptions] = useState<SelectProps['options']>([]);
const propsConfig = paramSelectConfig[dataType];
const {
getLabel,
getValue,
getLabel = identityFunc,
getValue = identityFunc,
getOptions,
filterOption,
fieldNames,
@@ -56,28 +57,43 @@ function ParameterSelect({
const valueText =
typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue;
const [resourceStandardList] = useComputingResource();
// const computingResource = isPipeline
// ? resourceStandardList.map((v) => ({
// ...v,
// id: String(v.id),
// }))
// : resourceStandardList;

const objectOptions = useMemo(() => {
return options?.map((v) => ({
label: getLabel(v),
value: getValue(v),
}));
}, [getLabel, getValue, options]);
const snap = useSnapshot(state);
const { getResourceTypes } = snap;

const objectOptions =
dataType === 'remote-resource-type'
? snap.types
: dataType === 'remote-image'
? snap.images
: dataType === 'remote-resource'
? snap.resources
: options;

// 将对象类型转换为 Select Options
const converObjectToOptions = useCallback(
(v: any) => {
return {
label: getLabel(v),
value: getValue(v),
};
},
[getLabel, getValue],
);

// 数据集、模型、服务获取数据后,进行转换
const objectSelectOptions = useMemo(() => {
return objectOptions?.map(converObjectToOptions);
}, [converObjectToOptions, objectOptions]);

// 快速得到选中的对象
const valueMap = useMemo(() => {
const map = new Map<string | number, any>();
options?.forEach((v) => {
objectOptions?.forEach((v) => {
map.set(getValue(v), v);
});

return map;
}, [options, getValue]);
}, [objectOptions, getValue]);

useEffect(() => {
// 获取下拉数据
@@ -87,13 +103,14 @@ function ParameterSelect({
if (res) {
setOptions(res);
}
} else if (dataType === 'remote-resource-type') {
getResourceTypes();
}
};

getSelectOptions();
}, [getOptions]);
}, [getOptions, dataType, getResourceTypes]);

const selectOptions = dataType === 'resource' ? resourceStandardList : objectOptions;
const selectOptions = dataType === 'resource' ? resourceStandardList : objectSelectOptions;

const handleChange = (text: string) => {
// 数据集、模型、服务,转换成对象


+ 44
- 7
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -1,7 +1,10 @@
import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal';
import KFIcon from '@/components/KFIcon';
import ParameterInput, { requiredValidator } from '@/components/ParameterInput';
import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect';
import ParameterSelect, {
type ParameterSelectDataType,
type ParameterSelectObject,
} from '@/components/ParameterSelect';
import ResourceSelectorModal, {
ResourceSelectorType,
selectorTypeConfig,
@@ -9,6 +12,7 @@ import ResourceSelectorModal, {
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys, ComponentType } from '@/enums';
import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils';
import state from '@/state/jcdResource';
import {
PipelineGlobalParam,
PipelineNodeModel,
@@ -20,6 +24,7 @@ import { to } from '@/utils/promise';
import { removeFormListItem } from '@/utils/ui';
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { INode } from '@antv/g6';
import { useSnapshot } from '@umijs/max';
import { Button, Drawer, Flex, Form, Input, MenuProps } from 'antd';
import { RuleObject } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface';
@@ -45,6 +50,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
);
const [open, setOpen] = useState(false);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);
const snap = useSnapshot(state);

const afterOpenChange = async () => {
if (!open) {
@@ -144,7 +150,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
formItemName: NamePath,
item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>,
) => {
if (item.item_type === 'code') {
if (item.item_type === 'code' || item.item_type === 'remote-code') {
selectCodeConfig(formItemName, item);
} else {
selectResource(formItemName, item);
@@ -183,9 +189,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
let type: ResourceSelectorType;
switch (item.item_type) {
case 'dataset':
case 'remote-dataset':
type = ResourceSelectorType.Dataset;
break;
case 'model':
case 'remote-model':
type = ResourceSelectorType.Model;
break;
default:
@@ -249,14 +257,14 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
// 获取选择数据集、模型后面按钮 icon
const getSelectBtnIcon = (item: { item_type: string }) => {
const type = item.item_type;
if (type === 'code') {
if (type === 'code' || type === 'remote-code') {
return <KFIcon type="icon-xuanzedaimapeizhi" />;
}

let selectorType: ResourceSelectorType;
if (type === 'dataset') {
if (type === 'dataset' || type === 'remote-dataset') {
selectorType = ResourceSelectorType.Dataset;
} else if (type === 'model') {
} else if (type === 'model' || type === 'remote-model') {
selectorType = ResourceSelectorType.Model;
} else {
selectorType = ResourceSelectorType.Mirror;
@@ -331,6 +339,21 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
return rules;
};

// 云际组件,选择类型后,重置镜像和资源,获取镜像、资源列表
const handleParameterSelect = (
value: ParameterSelectObject,
itemType: string,
parentName: string,
) => {
if (itemType === 'remote-resource-type') {
snap.setCurrentType(value.value);
const remoteImage = form.getFieldValue([parentName, '--image']);
form.setFieldValue([parentName, '--image'], { ...remoteImage, value: undefined });
const remoteResource = form.getFieldValue([parentName, '--resource']);
form.setFieldValue([parentName, '--resource'], { ...remoteResource, value: undefined });
}
};

// 表单组件
const getFormComponent = (
item: { key: string; value: PipelineNodeModelParameter },
@@ -361,12 +384,26 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
</Flex>
)}
{item.value.type === ComponentType.Select &&
(['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
([
'dataset',
'model',
'service',
'resource',
'remote-resource-type',
'remote-image',
'remote-resource',
].includes(item.value.item_type) ? (
<Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle>
<ParameterSelect
isPipeline
dataType={item.value.item_type as ParameterSelectDataType}
placeholder={item.value.placeholder}
onChange={(value) =>
handleParameterSelect(
value as ParameterSelectObject,
item.value.item_type,
parentName,
)
}
/>
</Form.Item>
) : null)}


+ 87
- 0
react-ui/src/services/external/index.ts View File

@@ -0,0 +1,87 @@
// 外部系统

import { request } from '@umijs/max';

// 云际系统登录
export function jccLoginReq() {
return request(`http://119.45.255.234:30180/jcc-admin/admin/login`, {
method: 'POST',
data: {
username: 'iflytek',
password: 'iflytek@123',
},
headers: {
isToken: false,
},
skipLoading: true,
skipValidating: true,
});
}

// 云际系统获取资源类型
export function jccGetResourceTypesReq(token: string, userId: number) {
return request(`http://119.45.255.234:30180/jsm/jobSet/resourceRange`, {
method: 'POST',
data: {
userID: userId,
},
headers: {
authorization: `${token}`,
isToken: false,
},
skipLoading: true,
skipValidating: true,
});
}

// 云际系统获取资源镜像
export function jccGetImagesReq(token: string, cardTypes: string[]) {
return request(`http://119.45.255.234:30180/jsm/jobSet/queryImages`, {
method: 'POST',
data: {
cardTypes: cardTypes,
},
headers: {
authorization: `${token}`,
isToken: false,
},
skipLoading: true,
skipValidating: true,
});
}

// 云际系统获取资源列表
export function jccGetResourcesReq(token: string, cardType: string) {
return request(`http://119.45.255.234:30180/jsm/jobSet/queryResource`, {
method: 'POST',
data: {
queryResource: {
cpu: {
min: 0,
max: 0,
},
memory: {
min: 0,
max: 0,
},
gpu: {
min: 0,
max: 0,
},
storage: {
min: 0,
max: 0,
},
type: cardType,
},
resourceType: 'Train',
clusterIDs: ['1865927992266461184', ''],
},
headers: {
authorization: `${token}`,
isToken: false,
},
skipLoading: true,
skipValidating: true,
});
}

+ 128
- 0
react-ui/src/state/jcdResource.ts View File

@@ -0,0 +1,128 @@
import {
jccGetImagesReq,
jccGetResourcesReq,
jccGetResourceTypesReq,
jccLoginReq,
} from '@/services/external';
import { to } from '@/utils/promise';
import { proxy } from '@umijs/max';

export type JCCResourceRange = {
type: string;
};

export type JCCResourceType = {
label: string;
value: string;
};

export interface JCCResourceImage {
imageID: number;
name: string;
createTime: Date;
clusterImages: JCCClusterImage[];
}

export interface JCCClusterImage {
imageID: number;
clusterID: string;
originImageType: string;
originImageID: string;
originImageName: string;
cards: JCCCard[];
}

export interface JCCCard {
originImageID: string;
card: string;
}

export interface JCCResourceStandard {
id: number;
sourceKey: string;
type: string;
name: string;
totalCount: number;
availableCount: number;
changeType: number;
status: number;
region: string;
clusterId: string;
costPerUnit: number;
costType: string;
tag: string;
userId: number;
createTime: Date;
updateTime: Date;
baseResourceSpecs: JCCBaseResourceSpec[];
}

export interface JCCBaseResourceSpec {
id: number;
resourceSpecId: number;
type: string;
name: string;
totalValue: number;
totalUnit: string;
availableValue: number;
availableUnit: string;
userId: number;
createTime: Date;
updateTime: Date;
}

type JCCResourceTypeStore = {
token: string;
types: JCCResourceType[];
images: JCCResourceImage[];
resources: JCCResourceStandard[];
currentType: string | undefined;
getResourceTypes: () => void;
getImages: (cardTypes: string[]) => void;
getResources: (cardType: string) => void;
setCurrentType: (cardType: string) => void;
};

const state = proxy<JCCResourceTypeStore>({
token: '',
types: [],
images: [],
resources: [],
currentType: undefined,
getResourceTypes: async () => {
const [loginRes] = await to(jccLoginReq());
if (loginRes && loginRes.code === 200 && loginRes.data) {
const { tokenHead, token, jsmUserInfo } = loginRes.data;
state.token = tokenHead + token;
const userID = jsmUserInfo?.data?.userID;
const [res] = await to(jccGetResourceTypesReq(tokenHead + token, userID));
if (res && res.code === 'OK' && res.data) {
state.types = res.data.resourceRanges?.map((v: JCCResourceRange) => ({
label: v.type,
value: v.type,
}));
}
}
},
getImages: async (cardTypes: string[]) => {
const [res] = await to(jccGetImagesReq(state.token, cardTypes));
if (res && res.code === 'OK' && res.data) {
state.images = res.data.images;
}
},
getResources: async (cardType: string) => {
const [res] = await to(jccGetResourcesReq(state.token, cardType));
if (res && res.code === 'OK' && res.data) {
state.resources = res.data.resource;
}
},
setCurrentType: (cardType: string) => {
state.currentType = cardType;
if (cardType) {
state.getImages([cardType]);
state.getResources(cardType);
}
},
});

export default state;

+ 2
- 0
react-ui/src/utils/sessionStorage.ts View File

@@ -13,6 +13,8 @@ export default class SessionStorage {
static readonly aimUrlKey = 'aim-url';
/** tensorBoard url */
static readonly tensorBoardUrlKey = 'tensor-board-url';
// /** 云际系统 Token */
// static readonly jccTokenKey = 'jcc-token';

/**
* 获取 SessionStorage 值


Loading…
Cancel
Save