{
defaultExpandedKeys: React.Key[];
defaultCheckedKeys: React.Key[];
defaultActiveTab: CommonTabKeys;
- onOk?: (params: ResourceSelectorResponse | null) => void;
+ onOk?: (params: ResourceSelectorResponse | string | null) => void;
}
type ResourceGroup = {
@@ -116,6 +152,13 @@ type ResourceGroup = {
name: string; // 数据集或者模型 id
};
+type MirrorVersion = {
+ id: number; // 镜像版本id
+ status: MirrorVersionStatus; // 镜像版本状态
+ tag_name: string; // 镜像版本
+ url: string; // 镜像版本路径
+};
+
type ResourceFile = {
id: number; // 文件 id
file_name: string; // 文件 name
@@ -133,6 +176,27 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
}));
};
+// 版本转成 treeData
+const convertVersionToTreeData = (parentId: number) => {
+ return (item: string | MirrorVersion): TreeDataNode => {
+ if (typeof item === 'string') {
+ return {
+ title: item,
+ key: `${parentId}-${item}`,
+ isLeaf: true,
+ checkable: true,
+ };
+ } else {
+ return {
+ title: item.tag_name,
+ key: `${parentId}-${item.id}-${item.url}`,
+ isLeaf: true,
+ checkable: true,
+ };
+ }
+ };
+};
+
// 更新树形结构的 children
const updateChildren = (parentId: number, children: TreeDataNode[]) => {
return (node: TreeDataNode) => {
@@ -197,11 +261,11 @@ function ResourceSelectorModal({
// 获取数据集或模型列表
const getTreeData = async () => {
- const available_range = activeTab === CommonTabKeys.Private ? '0' : '1';
+ const available_range = activeTab === CommonTabKeys.Private ? 0 : 1;
const params = {
page: 0,
size: 200,
- available_range: available_range,
+ [selectorTypeData[type].litReqParamKey]: available_range,
};
const getListReq = selectorTypeData[type].getList;
const [res] = await to(getListReq(params));
@@ -222,13 +286,8 @@ function ResourceSelectorModal({
const getVersionsReq = selectorTypeData[type].getVersions;
const [res, error] = await to(getVersionsReq(parentId));
if (res) {
- const list = res.data || [];
- const children = list.map((v: string) => ({
- title: v,
- key: `${parentId}-${v}`,
- isLeaf: true,
- checkable: true,
- }));
+ const list = selectorTypeData[type].handleVersionResponse(res);
+ const children = list.map(convertVersionToTreeData(parentId));
// 更新 treeData children
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
// 缓存 loadedKeys
@@ -248,7 +307,7 @@ function ResourceSelectorModal({
const getFiles = async (id: number, version: string) => {
const getFilesReq = selectorTypeData[type].getFiles;
const paramsKey = selectorTypeData[type].fileReqParamKey;
- const params = { version: version, [paramsKey]: id } as GetFilesReqParam;
+ const params = { version: version, [paramsKey]: id };
const [res] = await to(getFilesReq(params));
if (res) {
setVersionPath(res.data?.path || '');
@@ -329,17 +388,21 @@ function ResourceSelectorModal({
// 提交
const handleOk = () => {
if (checkedKeys.length > 0) {
- const last = checkedKeys[0] as string;
- const { id, version } = getIdAndVersion(last);
- const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
- const res = {
- id,
- name,
- path: versionPath,
- version,
- activeTab: activeTab as CommonTabKeys,
- };
- onOk?.(res);
+ if (type === ResourceSelectorType.Mirror) {
+ onOk?.(files[0].file_name);
+ } else {
+ const last = checkedKeys[0] as string;
+ const { id, version } = getIdAndVersion(last);
+ const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
+ const res = {
+ id,
+ name,
+ path: versionPath,
+ version,
+ activeTab: activeTab as CommonTabKeys,
+ };
+ onOk?.(res);
+ }
} else {
onOk?.(null);
}
@@ -347,7 +410,10 @@ function ResourceSelectorModal({
const title = `选择${selectorTypeData[type].name}`;
const palceholder = `请输入${selectorTypeData[type].name}名称`;
- const fileTitle = `已选${selectorTypeData[type].name}文件(${files.length})`;
+ const fileTitle =
+ type === ResourceSelectorType.Mirror
+ ? '已选镜像'
+ : `已选${selectorTypeData[type].name}文件(${files.length})`;
const tabItems = selectorTypeData[type].tabItems;
const titleImg = selectorTypeData[type].modalIcon;
diff --git a/react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx b/react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx
index fad63da3..82532a06 100644
--- a/react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx
+++ b/react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx
@@ -4,8 +4,9 @@ import {
} from '@/pages/Experiment/experimentText/addExperimentModal';
import { type PipelineGlobalParam } from '@/types';
import { to } from '@/utils/promise';
+import { modalConfirm } from '@/utils/ui';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
-import { Button, Drawer, Form, Input, Radio } from 'antd';
+import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle } from 'react';
import styles from './globalParamsDrawer.less';
@@ -22,9 +23,8 @@ const GlobalParamsDrawer = forwardRef(
useImperativeHandle(ref, () => ({
getFieldsValue: async () => {
- const [res, error] = await to(form.validateFields());
- if (res && !error) {
- const values = form.getFieldsValue();
+ const [values, error] = await to(form.validateFields());
+ if (!error && values) {
return values;
} else {
return Promise.reject(error);
@@ -32,10 +32,20 @@ const GlobalParamsDrawer = forwardRef(
},
}));
+ // 处理参数类型变化
const handleTypeChange = (name: NamePath) => {
form.setFieldValue(name, null);
};
+ const removeParameter = (name: number, remove: (param: number) => void) => {
+ modalConfirm({
+ title: '确认删除该参数吗?',
+ onOk: () => {
+ remove(name);
+ },
+ });
+ };
+
return (
否
-
+
+
+
))}
@@ -150,6 +162,7 @@ const GlobalParamsDrawer = forwardRef(
)}
+ {/* //{contextHolder} */}
);
},
diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx
index 5d31298a..948cd364 100644
--- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx
+++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx
@@ -1,9 +1,7 @@
-import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg';
-import { ReactComponent as SaveAndReturn } from '@/assets/svg/save--return.svg';
+import KFIcon from '@/components/KFIcon';
import { useVisible } from '@/hooks';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import { to } from '@/utils/promise';
-import { SaveOutlined } from '@ant-design/icons';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import G6 from '@antv/g6';
import { Button, message } from 'antd';
@@ -101,22 +99,32 @@ const EditPipeline = () => {
openParamsDrawer();
return;
}
- const data = graph.save();
- console.log(data);
- const params = {
- ...locationParams,
- dag: JSON.stringify(data),
- global_param: JSON.stringify(res.global_param),
- };
- saveWorkflow(params).then((ret) => {
- message.success('保存成功');
- closeParamsDrawer();
- setTimeout(() => {
- if (val) {
- navgite({ pathname: `/pipeline` });
- }
- }, 500);
- });
+ const [propsRes, propsError] = await to(propsRef.current.getFieldsValue());
+ console.log(await to(propsRef.current.getFieldsValue()));
+ if (propsError) {
+ message.error('基本信息必填项需配置');
+ // handlerClick();
+ return;
+ }
+ propsRef.current.propClose();
+ setTimeout(() => {
+ const data = graph.save();
+ console.log(data);
+ const params = {
+ ...locationParams,
+ dag: JSON.stringify(data),
+ global_param: JSON.stringify(res.global_param),
+ };
+ saveWorkflow(params).then((ret) => {
+ message.success('保存成功');
+ closeParamsDrawer();
+ setTimeout(() => {
+ if (val) {
+ navgite({ pathname: `/pipeline` });
+ }
+ }, 500);
+ });
+ }, 500);
};
const handlerClick = (e) => {
e.stopPropagation();
@@ -706,7 +714,7 @@ const EditPipeline = () => {
}
+ icon={}
style={{ marginRight: '20px' }}
onClick={openParamsDrawer}
>
@@ -714,7 +722,7 @@ const EditPipeline = () => {
}
+ icon={}
style={{ marginRight: '20px' }}
onClick={() => {
savePipeline(false);
@@ -730,7 +738,7 @@ const EditPipeline = () => {
background: '#fff',
color: '#1664ff',
}}
- icon={}
+ icon={}
onClick={() => {
savePipeline(true);
}}
diff --git a/react-ui/src/pages/Pipeline/editPipeline/props.jsx b/react-ui/src/pages/Pipeline/editPipeline/props.jsx
index b6f2c1f9..1756855d 100644
--- a/react-ui/src/pages/Pipeline/editPipeline/props.jsx
+++ b/react-ui/src/pages/Pipeline/editPipeline/props.jsx
@@ -1,17 +1,37 @@
+import KFIcon from '@/components/KFIcon';
+import { getComputingResourceReq } from '@/services/pipeline';
import { pick } from '@/utils/index';
import { openAntdModal } from '@/utils/modal';
-import { Icon } from '@umijs/max';
-import { Button, Drawer, Form, Input } from 'antd';
-import { forwardRef, useImperativeHandle, useState } from 'react';
+import { to } from '@/utils/promise';
+import { Button, Drawer, Form, Input, Select } from 'antd';
+import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
import Styles from './editPipeline.less';
const { TextArea } = Input;
const Props = forwardRef(({ onParentChange }, ref) => {
const [form] = Form.useForm();
+
const [stagingItem, setStagingItem] = useState({});
const [open, setOpen] = useState(false);
const [selectedModel, setSelectedModel] = useState(undefined);
const [selectedDataset, setSelectedDataset] = useState(undefined);
+ const [resourceStandardList, setResourceStandardList] = useState([]);
+
+ useEffect(() => {
+ getComputingResource();
+ }, []);
+
+ const getComputingResource = async () => {
+ const params = {
+ page: 0,
+ size: 1000,
+ resource_type: '',
+ };
+ const [res] = await to(getComputingResourceReq(params));
+ if (res && res.data && res.data.content) {
+ setResourceStandardList(res.data.content);
+ }
+ };
const afterOpenChange = () => {
if (!open) {
@@ -59,6 +79,15 @@ const Props = forwardRef(({ onParentChange }, ref) => {
console.log('Failed:', errorInfo);
};
useImperativeHandle(ref, () => ({
+ getFieldsValue: async () => {
+ const [propsRes, propsError] = await to(form.validateFields());
+ if (propsRes && !propsError) {
+ const values = form.getFieldsValue();
+ return values;
+ } else {
+ return Promise.reject(propsError);
+ }
+ },
showDrawer(e) {
if (e.item && e.item.getModel()) {
// console.log(e.item.getModel().in_parameters);
@@ -85,13 +114,34 @@ const Props = forwardRef(({ onParentChange }, ref) => {
setOpen(true);
}
},
+ propClose: async () => {
+ setOpen(false);
+ const [openRes, propsError] = await to(setOpen(false));
+ console.log(setOpen(false));
+ },
+ // propClose() {
+
+ // setOpen(false);
+ // },
}));
// 选择数据集、模型
const selectResource = (name, item) => {
- const type =
- item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model;
- const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel;
+ let type;
+ let resource = undefined;
+ switch (item.item_type) {
+ case 'dataset':
+ type = ResourceSelectorType.Dataset;
+ resource = selectedDataset;
+ break;
+ case 'model':
+ type = ResourceSelectorType.Model;
+ resource = selectedModel;
+ break;
+ default:
+ type = ResourceSelectorType.Mirror;
+ break;
+ }
const { close } = openAntdModal(
ResourceSelectorModal,
{
@@ -101,18 +151,23 @@ const Props = forwardRef(({ onParentChange }, ref) => {
defaultActiveTab: resource?.activeTab,
onOk: (res) => {
if (res) {
- const jsonObj = pick(res, ['id', 'version', 'path']);
- const value = JSON.stringify(jsonObj);
- form.setFieldValue(name, value);
+ if (type === ResourceSelectorType.Mirror) {
+ form.setFieldValue(name, res);
+ } else {
+ const jsonObj = pick(res, ['id', 'version', 'path']);
+ const value = JSON.stringify(jsonObj);
+ form.setFieldValue(name, value);
+ }
+
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
- } else {
+ } else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(null);
- } else {
+ } else if (type === ResourceSelectorType.Model) {
setSelectedModel(null);
}
form.setFieldValue(name, '');
@@ -128,14 +183,18 @@ const Props = forwardRef(({ onParentChange }, ref) => {
const getSelectBtnIcon = (item) => {
const type = item.item_type;
if (type === 'dataset') {
- return ;
+ return ;
} else if (type === 'model') {
- return ;
+ return ;
} else {
- return ;
+ return ;
}
};
+ const filterResourceStandard = (input, { computing_resource = '' }) => {
+ return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase());
+ };
+
// 控制策略
const controlStrategy = stagingItem.control_strategy;
// 输入参数
@@ -217,17 +276,22 @@ const Props = forwardRef(({ onParentChange }, ref) => {
/>
任务信息
-
-
+
+
+
+
+
+
+
+
+
@@ -242,11 +306,20 @@ const Props = forwardRef(({ onParentChange }, ref) => {
rules={[
{
required: true,
- message: '请输入资源规格',
+ message: '请选择资源规格',
},
]}
>
-
+
diff --git a/react-ui/src/pages/Pipeline/index.jsx b/react-ui/src/pages/Pipeline/index.jsx
index 7378bbc3..50206ace 100644
--- a/react-ui/src/pages/Pipeline/index.jsx
+++ b/react-ui/src/pages/Pipeline/index.jsx
@@ -1,3 +1,4 @@
+import KFIcon from '@/components/KFIcon';
import {
addWorkflow,
cloneWorkflow,
@@ -6,8 +7,9 @@ import {
getWorkflowById,
removeWorkflow,
} from '@/services/pipeline/index.js';
-import { CopyOutlined, DeleteOutlined, EditOutlined, PlusCircleOutlined } from '@ant-design/icons';
-import { Button, Form, Input, Modal, Space, Table, message } from 'antd';
+import themes from '@/styles/theme.less';
+import { modalConfirm } from '@/utils/ui';
+import { Button, ConfigProvider, Form, Input, Modal, Space, Table, message } from 'antd';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -26,7 +28,7 @@ const Pipeline = () => {
const editTable = (e, record) => {
e.stopPropagation();
getWorkflowById(record.id).then((ret) => {
- if (ret.code == 200) {
+ if (ret.code === 200) {
form.resetFields();
form.setFieldsValue({ ...ret.data });
setFormId(ret.data.id);
@@ -152,7 +154,7 @@ const Pipeline = () => {
type="link"
size="small"
key="edit"
- icon={}
+ icon={}
onClick={(e) => {
editTable(e, record);
}}
@@ -163,7 +165,7 @@ const Pipeline = () => {
type="link"
size="small"
key="clone"
- icon={}
+ icon={}
onClick={async () => {
Modal.confirm({
title: '复制',
@@ -173,7 +175,7 @@ const Pipeline = () => {
onOk: () => {
console.log(record);
cloneWorkflow(record.id).then((ret) => {
- if (ret.code == 200) {
+ if (ret.code === 200) {
message.success('复制成功');
getList();
} else {
@@ -192,54 +194,45 @@ const Pipeline = () => {
>
复制
- }
- onClick={async () => {
- Modal.confirm({
- title: (
-
-

-
- 删除后,该流水线将不可恢复
-
-
- ),
- content: 是否确认删除?
,
- closable: true,
-
- okText: '确认',
- cancelText: '取消',
- onOk: () => {
- console.log(record);
- removeWorkflow(record.id).then((ret) => {
- if (ret.code === 200) {
- message.success('删除成功');
- getList();
- } else {
- message.error(ret.msg);
- }
- });
-
- // if (success) {
- // if (actionRef.current) {
- // actionRef.current.reload();
- // }
- // }
- },
- });
+
- 删除
-
+ }
+ onClick={() => {
+ modalConfirm({
+ title: '删除后,该流水线将不可恢复',
+ content: '是否确认删除?',
+ onOk: () => {
+ console.log(record);
+ removeWorkflow(record.id).then((ret) => {
+ if (ret.code === 200) {
+ message.success('删除成功');
+ getList();
+ } else {
+ message.error(ret.msg);
+ }
+ });
+
+ // if (success) {
+ // if (actionRef.current) {
+ // actionRef.current.reload();
+ // }
+ // }
+ },
+ });
+ }}
+ >
+ 删除
+
+
),
},
@@ -251,7 +244,7 @@ const Pipeline = () => {
type="primary"
className={Styles.plusButton}
onClick={showModal}
- icon={}
+ icon={}
>
新建流水线
diff --git a/react-ui/src/services/pipeline/index.js b/react-ui/src/services/pipeline/index.js
index d722398b..7262d6b1 100644
--- a/react-ui/src/services/pipeline/index.js
+++ b/react-ui/src/services/pipeline/index.js
@@ -1,3 +1,8 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-03-25 13:52:54
+ * @Description:
+ */
import { request } from '@umijs/max';
// 查询流水线列表
export function getWorkflow(params) {
@@ -63,3 +68,11 @@ export function getWorkflowById(id) {
method: 'GET',
});
}
+
+// 获取资源规格
+export function getComputingResourceReq(params) {
+ return request(`/api/mmp/computingResource`, {
+ method: 'GET',
+ params
+ });
+}
diff --git a/react-ui/src/styles/theme.less b/react-ui/src/styles/theme.less
index 9f6aaace..64b78619 100644
--- a/react-ui/src/styles/theme.less
+++ b/react-ui/src/styles/theme.less
@@ -1,17 +1,32 @@
// 全局颜色变量
-// FIXME: 不能设置 @primary-color 不起作用,感觉是哪里被重置了
-@kf-primary-color: #1664ff; // 主色调
-@primary-color-hover: #4086ff;
+@primary-color: #1664ff; // 主色调
+@primary-color-hover: #69b1ff;
@background-color: #f9fafb; // 页面背景颜色
@text-color: #1d1d20;
-@text-color-second: #575757;
-@font-size: 15px;
+@text-color-secondary: #575757;
+@success-color: #1ace62;
+@error-color: #c73131;
+@warning-color: #f98e1b;
+
@border-color: rgba(22, 100, 255, 0.3);
@border-color-second: rgba(22, 100, 255, 0.1);
@background-color-primay: rgba(22, 100, 255, 0.03);
@background-color-gray: rgba(4, 3, 3, 0.06);
+@heading-color: rgba(0, 0, 0, 0.85);
+@input-icon-hover-color: rgba(0, 0, 0, 0.85);
+@border-color-base: #d9d9d9;
+@link-hover-color: #69b1ff;
+
+// 字体大小
+@font-size: 15px;
+
// 导出变量
:export {
- primaryColor: @kf-primary-color;
+ primaryColor: @primary-color;
+ successColor: @success-color;
+ errorColor: @error-color;
+ warningColor: @warning-color;
+ textColor: @text-color;
+ fontSize: @font-size;
}
diff --git a/react-ui/src/utils/modal.tsx b/react-ui/src/utils/modal.tsx
index d55d8378..4a3b765f 100644
--- a/react-ui/src/utils/modal.tsx
+++ b/react-ui/src/utils/modal.tsx
@@ -3,7 +3,8 @@
* @Date: 2024-04-13 10:08:35
* @Description:
*/
-import { type ModalProps } from 'antd';
+import { ConfigProvider, type ModalProps } from 'antd';
+import { globalConfig } from 'antd/es/config-provider';
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
@@ -19,19 +20,20 @@ export const openAntdModal = (
modalProps: T,
) => {
const CustomModel = modal;
- const element = document.createElement('div');
- element.id = 'modal-container';
- document.body.appendChild(element);
- const root = createRoot(element);
+ const container = document.createDocumentFragment();
+ const root = createRoot(container);
const { afterClose, onCancel } = modalProps;
+ const global = globalConfig();
+ let timeoutId: ReturnType;
function destroy() {
root.unmount();
- document.body.removeChild(element);
}
function handleAfterClose() {
afterClose?.();
+ // Warning: Attempted to synchronously unmount a root while React was already rendering.
+ // React cannot finish unmounting the root until the current render has completed, which may lead to a race condition.
setTimeout(() => {
destroy();
}, 0);
@@ -46,11 +48,26 @@ export const openAntdModal = (
}
function render(props: T) {
- root.render();
+ clearTimeout(timeoutId);
+
+ timeoutId = setTimeout(() => {
+ const rootPrefixCls = global.getPrefixCls();
+ const iconPrefixCls = global.getIconPrefixCls();
+ const theme = global.getTheme();
+ const dom = (
+
+ );
+
+ root.render(
+
+ {global.holderRender ? global.holderRender(dom) : dom}
+ ,
+ );
+ });
}
function close() {
- render({ ...modalProps, open: false, afterClose: handleAfterClose });
+ render({ ...modalProps, open: false });
}
render({ ...modalProps, open: true });
diff --git a/react-ui/src/utils/ui.tsx b/react-ui/src/utils/ui.tsx
new file mode 100644
index 00000000..79a4c1f8
--- /dev/null
+++ b/react-ui/src/utils/ui.tsx
@@ -0,0 +1,28 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-19 14:42:51
+ * @Description: UI 公共方法
+ */
+import themes from '@/styles/theme.less';
+import { Modal, type ModalFuncProps } from 'antd';
+
+// 自定义 Confirm 弹框
+export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) {
+ Modal.confirm({
+ ...rest,
+ title: (
+
+

+
{title}
+
+ ),
+ content: content && {content}
,
+ okText: '确认',
+ cancelText: '取消',
+ onOk: onOk,
+ });
+}