diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts
index 1351cd2c..7edf6db2 100644
--- a/react-ui/config/routes.ts
+++ b/react-ui/config/routes.ts
@@ -107,7 +107,12 @@ export default [
{
name: '开发环境',
path: '',
- component: './DevelopmentEnvironment/index',
+ component: './DevelopmentEnvironment/List',
+ },
+ {
+ name: '创建编辑器',
+ path: 'create',
+ component: './DevelopmentEnvironment/Create',
},
],
},
diff --git a/react-ui/src/assets/img/editor-parameter.png b/react-ui/src/assets/img/editor-parameter.png
new file mode 100644
index 00000000..b5fd9f41
Binary files /dev/null and b/react-ui/src/assets/img/editor-parameter.png differ
diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx
index 4b838b2d..0fc08551 100644
--- a/react-ui/src/components/ParameterInput/index.tsx
+++ b/react-ui/src/components/ParameterInput/index.tsx
@@ -43,7 +43,7 @@ function ParameterInput({
}
const isSelect = valueObj?.fromSelect;
const InputComponent = textArea ? Input.TextArea : Input;
- const placeholder = valueObj?.placeholder;
+ const placeholder = valueObj?.placeholder || rest?.placeholder;
return (
<>
diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts
index c0238791..b31aee3a 100644
--- a/react-ui/src/enums/index.ts
+++ b/react-ui/src/enums/index.ts
@@ -20,6 +20,7 @@ export enum ModelDeploymentStatus {
Pending = 'Pending', // 挂起中
}
+// 模型部署状态选项列表
export const modelDeploymentStatusOptions = [
{ label: '全部', value: '' },
{ label: '启动中', value: ModelDeploymentStatus.Init },
@@ -28,3 +29,12 @@ export const modelDeploymentStatusOptions = [
{ label: '失败', value: ModelDeploymentStatus.Failed },
{ label: '挂起中', value: ModelDeploymentStatus.Pending },
];
+
+// 开发环境编辑器状态
+export enum DevEditorStatus {
+ Pending = 'Pending', // 启动中
+ Running = 'Running', // 运行中
+ Terminated = 'Terminated', // 已终止
+ Failed = 'Failed', // 失败
+ Unknown = 'Unknown', // 未启动
+}
diff --git a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
index 9b20147a..5cb8b9a7 100644
--- a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
+++ b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
@@ -33,10 +33,11 @@ function AddVersionModal({
...rest
}: AddVersionModalProps) {
const [uuid] = useState(Date.now());
+ const config = resourceConfig[resourceType];
// 上传组件参数
const uploadProps: UploadProps = {
- action: resourceConfig[resourceType].uploadAction,
+ action: config.uploadAction,
headers: {
Authorization: getAccessToken() || '',
},
@@ -45,7 +46,7 @@ function AddVersionModal({
// 上传请求
const createDatasetVersion = async (params: any) => {
- const request = resourceConfig[resourceType].addVersion;
+ const request = config.addVersion;
const [res] = await to(request(params));
if (res) {
message.success('创建成功');
@@ -62,7 +63,7 @@ function AddVersionModal({
const data = item.response?.data?.[0] ?? {};
return {
...otherParams,
- [resourceConfig[resourceType].idParamKey]: resourceId,
+ [config.idParamKey]: resourceId,
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
@@ -72,8 +73,8 @@ function AddVersionModal({
}
};
- const name = resourceConfig[resourceType].name;
- const accept = resourceConfig[resourceType].uploadAccept;
+ const name = config.name;
+ const accept = config.uploadAccept;
return (
{item.name}
diff --git a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx
index 87980da7..8582168e 100644
--- a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx
+++ b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx
@@ -29,15 +29,14 @@ function CategoryList({
onTagSelect,
onSearch,
}: CategoryProps) {
+ const config = resourceConfig[resourceType];
return (
-
- {resourceConfig[resourceType].typeTitle}
-
+
{config.typeTitle}
{typeList?.map((item) => (
- {resourceConfig[resourceType].tagTitle}
+ {config.tagTitle}
{tagList?.map((item) => (
diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
index 91a63582..bcbd123f 100644
--- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
+++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
@@ -28,16 +28,17 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
const [version, setVersion] = useState(undefined);
const [activeTab, setActiveTab] = useState(defaultTab);
const resourceId = Number(locationParams.id);
- const typeName = resourceConfig[resourceType].name; // 数据集/模型
+ const config = resourceConfig[resourceType];
+ const typeName = config.name; // 数据集/模型
useEffect(() => {
getModelByDetail();
getVersionList();
- }, []);
+ }, [resourceId]);
// 获取详情
const getModelByDetail = async () => {
- const request = resourceConfig[resourceType].getInfo;
+ const request = config.getInfo;
const [res] = await to(request(resourceId));
if (res) {
setInfo(res.data);
@@ -46,7 +47,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
// 获取版本列表
const getVersionList = async () => {
- const request = resourceConfig[resourceType].getVersions;
+ const request = config.getVersions;
const [res] = await to(request(resourceId));
if (res && res.data && res.data.length > 0) {
setVersionList(
@@ -122,10 +123,8 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
});
}
- const infoTypePropertyName = resourceConfig[resourceType]
- .infoTypePropertyName as keyof ResourceData;
- const infoTagPropertyName = resourceConfig[resourceType]
- .infoTagPropertyName as keyof ResourceData;
+ const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData;
+ const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData;
return (
diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx
index 830a9e1c..e7891141 100644
--- a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx
+++ b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx
@@ -54,6 +54,7 @@ function ResourceList(
const [searchText, setSearchText] = useState(initialSearchText);
const [inputText, setInputText] = useState(initialSearchText);
const { message } = App.useApp();
+ const config = resourceConfig[resourceType];
useEffect(() => {
getDataList();
@@ -82,12 +83,12 @@ function ResourceList(
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
- [resourceConfig[resourceType].typeParamKey]: dataType,
- [resourceConfig[resourceType].tagParamKey]: dataTag,
+ [config.typeParamKey]: dataType,
+ [config.tagParamKey]: dataTag,
available_range: isPublic ? 1 : 0,
name: searchText !== '' ? searchText : undefined,
};
- const request = resourceConfig[resourceType].getList;
+ const request = config.getList;
const [res] = await to(request(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
@@ -97,7 +98,7 @@ function ResourceList(
// 删除请求
const deleteRecord = async (id: number) => {
- const request = resourceConfig[resourceType].deleteRecord;
+ const request = config.deleteRecord;
const [res] = await to(request(id));
if (res) {
getDataList();
@@ -113,7 +114,7 @@ function ResourceList(
// 删除
const handleRemove = (record: ResourceData) => {
modalConfirm({
- title: resourceConfig[resourceType].deleteModalTitle,
+ title: config.deleteModalTitle,
onOk: () => {
deleteRecord(record.id);
},
@@ -129,7 +130,7 @@ function ResourceList(
activeType: dataType,
activeTag: dataTag,
});
- const prefix = resourceConfig[resourceType].prefix;
+ const prefix = config.prefix;
navigate(`/dataset/${prefix}/${record.id}`);
};
@@ -176,7 +177,7 @@ function ResourceList(
onClick={showModal}
icon={}
>
- {resourceConfig[resourceType].addBtnTitle}
+ {config.addBtnTitle}
)}
diff --git a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
index d5e1ed43..e5180a01 100644
--- a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
+++ b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
@@ -21,6 +21,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
const [activeType, setActiveType] = useState(cacheState?.activeType);
const [activeTag, setActiveTag] = useState(cacheState?.activeTag);
const dataListRef = useRef(null);
+ const config = resourceConfig[resourceType];
useEffect(() => {
getAssetIconList();
@@ -52,16 +53,10 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
if (res && res.data && res.data.content) {
const { content } = res.data;
setTypeList(
- content.filter(
- (item: CategoryData) =>
- Number(item.category_id) === resourceConfig[resourceType].typeValue,
- ),
+ content.filter((item: CategoryData) => Number(item.category_id) === config.typeValue),
);
setTagList(
- content.filter(
- (item: CategoryData) =>
- Number(item.category_id) === resourceConfig[resourceType].tagValue,
- ),
+ content.filter((item: CategoryData) => Number(item.category_id) === config.tagValue),
);
}
};
@@ -76,11 +71,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
return (
-
+
([]);
const { message } = App.useApp();
+ const config = resourceConfig[resourceType];
// 获取版本文件列表
useEffectWhen(
@@ -59,9 +60,9 @@ function ResourceVersion({
const getFileList = async (version: string) => {
const params = {
version,
- [resourceConfig[resourceType].fileReqParamKey]: resourceId,
+ [config.fileReqParamKey]: resourceId,
};
- const request = resourceConfig[resourceType].getFiles;
+ const request = config.getFiles;
const [res] = await to(request(params));
if (res) {
setFileList(res?.data?.content ?? []);
@@ -70,9 +71,9 @@ function ResourceVersion({
// 删除版本
const deleteVersion = async () => {
- const request = resourceConfig[resourceType].deleteVersion;
+ const request = config.deleteVersion;
const params = {
- [resourceConfig[resourceType].idParamKey]: resourceId,
+ [config.idParamKey]: resourceId,
version,
};
const [res] = await to(request(params));
@@ -111,13 +112,13 @@ function ResourceVersion({
// 全部导出
const handleExport = async () => {
- const url = resourceConfig[resourceType].downloadAllAction;
+ const url = config.downloadAllAction;
downLoadZip(url, { models_id: resourceId, version });
};
// 单个导出
const downloadAlone = (record: ResourceFileData) => {
- const url = resourceConfig[resourceType].downloadSingleAction;
+ const url = config.downloadSingleAction;
downLoadZip(`${url}/${record.id}`);
};
diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.less b/react-ui/src/pages/DevelopmentEnvironment/Create/index.less
new file mode 100644
index 00000000..cd1dcb27
--- /dev/null
+++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.less
@@ -0,0 +1,17 @@
+.editor-create {
+ height: 100%;
+
+ &__content {
+ height: calc(100% - 60px);
+ margin-top: 10px;
+ padding: 30px 30px 10px;
+ overflow: auto;
+ background-color: white;
+ border-radius: 10px;
+
+ &__type {
+ color: @text-color;
+ font-size: @font-size-input-lg;
+ }
+ }
+}
diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
new file mode 100644
index 00000000..036fc12c
--- /dev/null
+++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
@@ -0,0 +1,344 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-16 13:58:08
+ * @Description: 创建镜像
+ */
+import KFIcon from '@/components/KFIcon';
+import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
+import PageTitle from '@/components/PageTitle';
+import ParameterInput from '@/components/ParameterInput';
+import SubAreaTitle from '@/components/SubAreaTitle';
+import { useComputingResource } from '@/hooks/resource';
+import ResourceSelectorModal, {
+ ResourceSelectorResponse,
+ ResourceSelectorType,
+ selectorTypeConfig,
+} from '@/pages/Pipeline/components/ResourceSelectorModal';
+import { createEditorReq } from '@/services/developmentEnvironment';
+import { openAntdModal } from '@/utils/modal';
+import { to } from '@/utils/promise';
+import { useNavigate } from '@umijs/max';
+import { App, Button, Col, Form, Input, Row, Select } from 'antd';
+import { pick } from 'lodash';
+import { useState } from 'react';
+import styles from './index.less';
+
+type FormData = {
+ name: string;
+ computing_resource: string;
+ standard: string;
+ image: string;
+ model: ResourceSelectorResponse;
+ dataset: ResourceSelectorResponse;
+};
+
+enum ComputingResourceType {
+ GPU = 'GPU',
+ NPU = 'NPU',
+}
+
+const EditorRadioItems: KFRadioItem[] = [
+ {
+ key: ComputingResourceType.GPU,
+ title: '英伟达GPU',
+ icon: ,
+ },
+ {
+ key: ComputingResourceType.NPU,
+ title: '昇腾NPU',
+ icon: ,
+ },
+];
+
+function EditorCreate() {
+ const navgite = useNavigate();
+ const [form] = Form.useForm();
+ const { message } = App.useApp();
+ const [resourceStandardList, filterResourceStandard] = useComputingResource();
+ const [selectedModel, setSelectedModel] = useState(
+ undefined,
+ ); // 选择的模型,为了再次打开时恢复原来的选择
+ const [selectedDataset, setSelectedDataset] = useState(
+ undefined,
+ ); // 选择的数据集,为了再次打开时恢复原来的选择
+ const [selectedMirror, setSelectedMirror] = useState(
+ undefined,
+ ); // 选择的镜像,为了再次打开时恢复原来的选择
+
+ // 创建编辑器
+ const createEditor = async (formData: FormData) => {
+ // const { model, dataset } = formData;
+ // const params = {
+ // ...formData,
+ // model: JSON.stringify(omit(model, ['showValue'])),
+ // dataset: JSON.stringify(dataset, ['showValue']),
+ // };
+ const [res] = await to(createEditorReq(formData));
+ if (res) {
+ message.success('创建成功');
+ navgite(-1);
+ }
+ };
+
+ // 提交
+ const handleSubmit = (values: FormData) => {
+ createEditor(values);
+ };
+
+ // 取消
+ const cancel = () => {
+ navgite(-1);
+ };
+ // 获取选择数据集、模型后面按钮 icon
+ const getSelectBtnIcon = (type: ResourceSelectorType) => {
+ return ;
+ };
+
+ // 选择模型、镜像、数据集
+ const selectResource = (name: string, type: ResourceSelectorType) => {
+ let resource: ResourceSelectorResponse | undefined;
+ switch (type) {
+ case ResourceSelectorType.Model:
+ resource = selectedModel;
+ break;
+ case ResourceSelectorType.Dataset:
+ resource = selectedDataset;
+ break;
+ default:
+ resource = selectedMirror;
+ break;
+ }
+ const { close } = openAntdModal(ResourceSelectorModal, {
+ type,
+ defaultExpandedKeys: resource ? [resource.id] : [],
+ defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
+ defaultActiveTab: resource?.activeTab,
+ onOk: (res) => {
+ if (res) {
+ if (type === ResourceSelectorType.Mirror) {
+ form.setFieldValue(name, res.path);
+ setSelectedMirror(res);
+ } else {
+ const showValue = `${res.name}:${res.version}`;
+ form.setFieldValue(name, {
+ ...pick(res, ['id', 'version', 'path']),
+ showValue,
+ });
+ if (type === ResourceSelectorType.Model) {
+ setSelectedModel(res);
+ } else if (type === ResourceSelectorType.Dataset) {
+ setSelectedDataset(res);
+ }
+ }
+ } else {
+ if (type === ResourceSelectorType.Model) {
+ setSelectedModel(undefined);
+ } else if (type === ResourceSelectorType.Dataset) {
+ setSelectedDataset(undefined);
+ } else if (type === ResourceSelectorType.Mirror) {
+ setSelectedMirror(undefined);
+ }
+ form.setFieldValue(name, '');
+ }
+ close();
+ },
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default EditorCreate;
diff --git a/react-ui/src/pages/DevelopmentEnvironment/List/index.less b/react-ui/src/pages/DevelopmentEnvironment/List/index.less
new file mode 100644
index 00000000..d29e8a2d
--- /dev/null
+++ b/react-ui/src/pages/DevelopmentEnvironment/List/index.less
@@ -0,0 +1,22 @@
+.develop-env {
+ height: 100%;
+ &__header {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ height: 50px;
+ margin-bottom: 10px;
+ padding: 0 30px;
+ background-image: url(@/assets/img/page-title-bg.png);
+ background-repeat: no-repeat;
+ background-position: top center;
+ background-size: 100% 100%;
+ }
+
+ &__table {
+ height: calc(100% - 60px);
+ padding: 20px 30px 0;
+ background-color: white;
+ border-radius: 10px;
+ }
+}
diff --git a/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
new file mode 100644
index 00000000..9c4badb6
--- /dev/null
+++ b/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
@@ -0,0 +1,263 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-16 13:58:08
+ * @Description: 开发环境
+ */
+import CommonTableCell from '@/components/CommonTableCell';
+import DateTableCell from '@/components/DateTableCell';
+import KFIcon from '@/components/KFIcon';
+import { DevEditorStatus } from '@/enums';
+import { useCacheState } from '@/hooks/pageCacheState';
+import {
+ deleteEditorReq,
+ getEditorListReq,
+ startEditorReq,
+ stopEditorReq,
+} from '@/services/developmentEnvironment';
+import themes from '@/styles/theme.less';
+import { to } from '@/utils/promise';
+import { modalConfirm } from '@/utils/ui';
+import { useNavigate } from '@umijs/max';
+import {
+ App,
+ Button,
+ ConfigProvider,
+ Table,
+ type TablePaginationConfig,
+ type TableProps,
+} from 'antd';
+import classNames from 'classnames';
+import { useEffect, useState } from 'react';
+import EditorStatusCell from '../components/EditorStatusCell';
+import styles from './index.less';
+
+export type EditorData = {
+ id: number;
+ name: string;
+ status: string;
+ computing_resource: string;
+ update_by: string;
+ create_time: string;
+};
+
+function EditorList() {
+ const navigate = useNavigate();
+ const [cacheState, setCacheState] = useCacheState();
+ const { message } = App.useApp();
+ const [tableData, setTableData] = useState([]);
+ const [total, setTotal] = useState(0);
+ const [pagination, setPagination] = useState(
+ cacheState?.pagination ?? {
+ current: 1,
+ pageSize: 10,
+ },
+ );
+
+ useEffect(() => {
+ getEditorList();
+ }, [pagination]);
+
+ // 获取编辑器列表
+ const getEditorList = async () => {
+ const params: Record = {
+ page: pagination.current! - 1,
+ size: pagination.pageSize,
+ };
+ const [res] = await to(getEditorListReq(params));
+ if (res && res.data) {
+ const { content = [], totalElements = 0 } = res.data;
+ setTableData(content);
+ setTotal(totalElements);
+ }
+ };
+
+ // 删除编辑器
+ const deleteEditor = async (id: number) => {
+ const [res] = await to(deleteEditorReq(id));
+ if (res) {
+ message.success('删除成功');
+ // 如果是一页的唯一数据,删除时,请求第一页的数据
+ // 否则直接刷新这一页的数据
+ // 避免回到第一页
+ if (tableData.length > 1) {
+ setPagination((prev) => ({
+ ...prev,
+ current: 1,
+ }));
+ } else {
+ getEditorList();
+ }
+ }
+ };
+
+ // 启动编辑器
+ const startEditor = async (id: number) => {
+ const [res] = await to(startEditorReq(id));
+ if (res) {
+ message.success('操作成功');
+ getEditorList();
+ }
+ };
+
+ // 停止编辑器
+ const stopEditor = async (id: number) => {
+ const [res] = await to(stopEditorReq(id));
+ if (res) {
+ message.success('操作成功');
+ getEditorList();
+ }
+ };
+
+ // 处理删除
+ const handleEditorDelete = (record: EditorData) => {
+ modalConfirm({
+ title: '删除后,该编辑器将不可恢复',
+ content: '是否确认删除?',
+ onOk: () => {
+ deleteEditor(record.id);
+ },
+ });
+ };
+
+ // 创建编辑器
+ const createEditor = () => {
+ navigate(`/developmentEnvironment/create`);
+ setCacheState({
+ pagination,
+ });
+ };
+
+ // 分页切换
+ const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
+ if (action === 'paginate') {
+ setPagination(pagination);
+ }
+ };
+
+ const columns: TableProps['columns'] = [
+ {
+ title: '编辑器名称',
+ dataIndex: 'name',
+ key: 'name',
+ width: '30%',
+ render: CommonTableCell(),
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ key: 'status',
+ width: '10%',
+ render: EditorStatusCell,
+ },
+ {
+ title: '资源',
+ dataIndex: 'computing_resource',
+ key: 'computing_resource',
+ width: '20%',
+ render: CommonTableCell(),
+ },
+ {
+ title: '创建者',
+ dataIndex: 'update_by',
+ key: 'update_by',
+ width: '20%',
+ render: CommonTableCell(),
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'create_time',
+ key: 'create_time',
+ width: '20%',
+ render: DateTableCell,
+ },
+ {
+ title: '操作',
+ dataIndex: 'operation',
+ width: 300,
+ key: 'operation',
+ render: (_: any, record: EditorData) => (
+
+ {record.status === DevEditorStatus.Pending ||
+ record.status === DevEditorStatus.Running ? (
+ }
+ onClick={() => stopEditor(record.id)}
+ >
+ 停止
+
+ ) : (
+ }
+ onClick={() => startEditor(record.id)}
+ >
+ 再次调试
+
+ )}
+
+ }
+ onClick={() => handleEditorDelete(record)}
+ >
+ 删除
+
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
+ }
+ >
+ 创建编辑器
+
+ }
+ >
+ 刷新
+
+
+
+
+ );
+}
+
+export default EditorList;
diff --git a/react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.less b/react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.less
new file mode 100644
index 00000000..b2e46d49
--- /dev/null
+++ b/react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.less
@@ -0,0 +1,19 @@
+.model-deployment-status-cell {
+ color: @text-color;
+
+ &--running {
+ color: @primary-color;
+ }
+
+ &--terminated {
+ color: @abort-color;
+ }
+
+ &--error {
+ color: @error-color;
+ }
+
+ &--pending {
+ color: @warning-color;
+ }
+}
diff --git a/react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.tsx
new file mode 100644
index 00000000..90ad8c75
--- /dev/null
+++ b/react-ui/src/pages/DevelopmentEnvironment/components/EditorStatusCell/index.tsx
@@ -0,0 +1,44 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-18 18:35:41
+ * @Description: 编辑器状态组件
+ */
+import { DevEditorStatus } from '@/enums';
+import styles from './index.less';
+
+export type DevEditorStatusInfo = {
+ text: string;
+ classname: string;
+};
+
+export const statusInfo: Record = {
+ [DevEditorStatus.Unknown]: {
+ text: '未启动',
+ classname: styles['model-deployment-status-cell'],
+ },
+ [DevEditorStatus.Running]: {
+ classname: styles['model-deployment-status-cell--running'],
+ text: '运行中',
+ },
+ [DevEditorStatus.Terminated]: {
+ classname: styles['model-deployment-status-cell--terminated'],
+ text: '已停止',
+ },
+ [DevEditorStatus.Failed]: {
+ classname: styles['model-deployment-status-cell--error'],
+ text: '失败',
+ },
+ [DevEditorStatus.Pending]: {
+ classname: styles['model-deployment-status-cell--pending'],
+ text: '启动中',
+ },
+};
+
+function EditorStatusCell(status?: DevEditorStatus | null) {
+ if (status === null || status === undefined || !statusInfo[status]) {
+ return --;
+ }
+ return {statusInfo[status].text};
+}
+
+export default EditorStatusCell;
diff --git a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less
index eec152a7..2470e868 100644
--- a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less
+++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less
@@ -1,4 +1,4 @@
-.modal {
+.add-experiment-modal {
.global_param_item {
max-height: 230px;
padding: 24px 12px 0;
diff --git a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
index 71ec2f06..becfc0a7 100644
--- a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
+++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
@@ -115,7 +115,7 @@ function AddExperimentModal({
};
return (
{
- downLoadZip(`/api/mmp/minioStorage/download`, { path: val });
+ const { message } = App.useApp();
+
+ // 下载
+ const download = (path: string) => {
+ downLoadZip(`/api/mmp/minioStorage/download`, { path });
+ };
+
+ // 导出到模型库
+ const exportToModel = (path: string) => {
+ const { close } = openAntdModal(ExportModelModal, {
+ path,
+ onOk: () => {
+ message.success('导出成功');
+ close();
+ },
+ });
};
return (
@@ -31,12 +48,22 @@ function ExperimentResult({ results }: ExperimentResultProps) {
+
{/* 导出到模型库
导出到数据集 */}
diff --git a/react-ui/src/pages/Experiment/components/ExportModelModal/index.less b/react-ui/src/pages/Experiment/components/ExportModelModal/index.less
new file mode 100644
index 00000000..250f56a3
--- /dev/null
+++ b/react-ui/src/pages/Experiment/components/ExportModelModal/index.less
@@ -0,0 +1,7 @@
+.export-model-modal__tooltip {
+ :global {
+ .ant-tooltip-inner {
+ white-space: pre-line;
+ }
+ }
+}
diff --git a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx
new file mode 100644
index 00000000..b1d09da0
--- /dev/null
+++ b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx
@@ -0,0 +1,207 @@
+import editExperimentIcon from '@/assets/img/edit-experiment.png';
+import KFModal from '@/components/KFModal';
+import { type ResourceData } from '@/pages/Dataset/config';
+import {
+ addModelsVersionDetail,
+ exportModelReq,
+ getModelList,
+ getModelVersionsById,
+} from '@/services/dataset';
+import { to } from '@/utils/promise';
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { Form, Input, ModalProps, Select } from 'antd';
+import { useEffect, useState } from 'react';
+import styles from './index.less';
+
+type FormData = {
+ models_id: string;
+ version: string;
+ description: string;
+};
+
+type ExportModelResponce = {
+ fileName: string;
+ fileSize: string;
+ url: string;
+};
+
+type CreateModelVersionParams = FormData & {
+ file_name: string;
+ file_size: string;
+ url: string;
+ // name: string;
+};
+
+interface ExportModelModalProps extends Omit {
+ path: string;
+ onOk: () => void;
+}
+
+function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
+ const [form] = Form.useForm();
+ const [models, setModels] = useState([]);
+ const [versions, setVersions] = useState([]);
+ const [uuid] = useState(Date.now());
+
+ const layout = {
+ labelCol: { span: 24 },
+ wrapperCol: { span: 24 },
+ };
+
+ useEffect(() => {
+ requestModelList();
+ }, []);
+
+ // 模型版本tooltip
+ const getTooltip = () => {
+ const id = form.getFieldValue('models_id');
+ const name = models.find((item) => item.id === id)?.name ?? '';
+ const tooltip =
+ versions.length > 0 ? `${name}有以下版本:\n${versions.join('、')}\n注意不能重复` : undefined;
+ return tooltip;
+ };
+
+ // 处理模型名称变化
+ const handleModelChange = (id: number | undefined) => {
+ if (id) {
+ getModelVersions(id);
+ } else {
+ setVersions([]);
+ }
+ };
+
+ // 获取模型列表
+ const requestModelList = async () => {
+ const params = {
+ page: 0,
+ size: 1000,
+ available_range: 0, // 个人
+ };
+ const [res] = await to(getModelList(params));
+ if (res && res.data) {
+ setModels(res.data.content || []);
+ }
+ };
+
+ // 获取模型版本列表
+ const getModelVersions = async (id: number) => {
+ const [res] = await to(getModelVersionsById(id));
+ if (res && res.data) {
+ setVersions(res.data);
+ }
+ };
+
+ // 提交
+ const hanldeFinish = (formData: FormData) => {
+ exportToModel(formData);
+ };
+
+ // 导出到模型
+ const exportToModel = async (formData: FormData) => {
+ const params = {
+ uuid: String(uuid),
+ path,
+ };
+ const [res] = await to(exportModelReq(params));
+ if (res && res.data) {
+ const files = res.data as ExportModelResponce[];
+ const params: CreateModelVersionParams[] = files.map((item) => ({
+ ...formData,
+ file_name: item.fileName,
+ file_size: item.fileSize,
+ url: item.url,
+ }));
+
+ createModelVersion(params);
+ }
+ };
+
+ // 创建模型版本
+ const createModelVersion = async (params: CreateModelVersionParams[]) => {
+ const [res] = await to(addModelsVersionDetail(params));
+ if (res) {
+ onOk();
+ }
+ };
+
+ return (
+
+
+
+
+ ,
+ }
+ : undefined
+ }
+ rules={[
+ { required: true, message: '请输入模型版本' },
+ {
+ validator: (_, value) => {
+ if (value && versions.includes(value)) {
+ return Promise.reject('模型版本已存在');
+ } else {
+ return Promise.resolve();
+ }
+ },
+ },
+ ]}
+ >
+
+
+
+
+
+
+
+ );
+}
+
+export default ExportModelModal;
diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx
index 991e9855..db217197 100644
--- a/react-ui/src/pages/Experiment/training/index.jsx
+++ b/react-ui/src/pages/Experiment/training/index.jsx
@@ -21,8 +21,8 @@ function ExperimentText() {
const navgite = useNavigate();
const locationParams = useParams(); //新版本获取路由参数接口
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);
-
const graphRef = useRef();
+
const getGraphData = (data) => {
if (graph) {
// 修改历史数据有蓝色边框的问题
@@ -202,9 +202,6 @@ function ExperimentText() {
},
},
defaultEdge: {
- // type: 'quadratic',
- // type: 'cubic-vertical',
-
style: {
endArrow: {
// 设置终点箭头
@@ -226,16 +223,6 @@ function ExperimentText() {
},
},
},
- defaultCombo: {
- type: 'rect',
- fixCollapseSize: 70,
- style: {
- fill: '#00e0ff0d',
- stroke: '#00e0ff',
- lineDash: [5, 10],
- cursor: 'pointer',
- },
- },
});
graph.on('node:click', (e) => {
if (e.target.get('name') !== 'anchor-point' && e.item) {
diff --git a/react-ui/src/pages/Mirror/Info/index.less b/react-ui/src/pages/Mirror/Info/index.less
index 650310b6..adee2d49 100644
--- a/react-ui/src/pages/Mirror/Info/index.less
+++ b/react-ui/src/pages/Mirror/Info/index.less
@@ -31,23 +31,5 @@
display: flex;
align-items: center;
}
-
- &__table {
- :global {
- .ant-table-wrapper {
- height: 100%;
- .ant-spin-nested-loading {
- height: 100%;
- }
- .ant-spin-container {
- height: 100%;
- }
- .ant-table {
- height: calc(100% - 74px);
- overflow: auto;
- }
- }
- }
- }
}
}
diff --git a/react-ui/src/pages/Mirror/Info/index.tsx b/react-ui/src/pages/Mirror/Info/index.tsx
index c99c919d..60e19ab0 100644
--- a/react-ui/src/pages/Mirror/Info/index.tsx
+++ b/react-ui/src/pages/Mirror/Info/index.tsx
@@ -32,7 +32,6 @@ import {
type TablePaginationConfig,
type TableProps,
} from 'antd';
-import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
import MirrorStatusCell from '../components/MirrorStatusCell';
import styles from './index.less';
@@ -282,7 +281,7 @@ function MirrorInfo() {
{(showNodeTooltip || enterTooltip) && (
diff --git a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx
index 3c3f4225..a878321a 100644
--- a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx
+++ b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx
@@ -3,8 +3,8 @@ import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util }
// @ts-ignore
import Hierarchy from '@antv/hierarchy';
-export const nodeWidth = 110;
-export const nodeHeight = 50;
+export const nodeWidth = 90;
+export const nodeHeight = 40;
export const vGap = nodeHeight + 20;
export const hGap = nodeWidth;
export const ellipseWidth = nodeWidth;
@@ -64,6 +64,7 @@ export type ModalDetail = {
export interface ModelDepsAPIData {
current_model_id: number;
version: string;
+ workflow_id: number;
exp_ins_id: number;
model_type: NodeType.children | NodeType.current | NodeType.parent;
current_model_name: string;
diff --git a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx
index a4b3f13b..217222da 100644
--- a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx
+++ b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx
@@ -1,20 +1,35 @@
+import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro';
import { formatDate } from '@/utils/date';
+import { useNavigate } from '@umijs/max';
import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils';
import styles from './index.less';
-type NodeTooltipsProps = {
- data?: ModelDepsData | ProjectDependency | TrainDataset;
- x: number;
- y: number;
- onMouseEnter?: () => void;
- onMouseLeave?: () => void;
+type ModelInfoProps = {
+ resourceId: number;
+ data: ModelDepsData;
+ onVersionChange: (version: string) => void;
};
-function ModelInfo({ data }: { data: ModelDepsData }) {
+function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
+ const navigate = useNavigate();
+
const gotoExperimentPage = () => {
if (data.train_task?.ins_id) {
const { origin } = location;
- window.open(`${origin}/pipeline/experiment/144/${data.train_task.ins_id}`, '_blank');
+ const url = `${origin}/pipeline/experiment/${data.workflow_id}/${data.train_task.ins_id}`;
+ window.open(url, '_blank');
+ }
+ };
+
+ const gotoModelPage = () => {
+ if (data.model_type === NodeType.current) {
+ return;
+ }
+ if (data.current_model_id === resourceId) {
+ onVersionChange?.(data.version);
+ } else {
+ const path = `/dataset/model/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`;
+ navigate(path);
}
};
@@ -24,9 +39,18 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
模型名称:
-
- {data.model_version_dependcy_vo.name || '--'}
-
+ {data.model_type === NodeType.current ? (
+
+ {data.model_version_dependcy_vo?.name || '--'}
+
+ ) : (
+
+ )}
模型版本:
@@ -61,13 +85,12 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
>
@@ -75,13 +98,24 @@ function ModelInfo({ data }: { data: ModelDepsData }) {
}
function DatasetInfo({ data }: { data: TrainDataset }) {
+ const gotoDatasetPage = () => {
+ const { origin } = location;
+ const url = `${origin}/dataset/dataset/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`;
+ window.open(url, '_blank');
+ };
+
return (
<>
数据集信息
数据集名称:
- {data.dataset_name || '--'}
+
数据集版本:
@@ -95,13 +129,23 @@ function DatasetInfo({ data }: { data: TrainDataset }) {
}
function ProjectInfo({ data }: { data: ProjectDependency }) {
+ const gotoProjectPage = () => {
+ const { url } = data;
+ window.open(url, '_blank');
+ };
+
return (
<>
项目信息
项目名称:
- {data.name || '--'}
+
项目分支:
@@ -116,7 +160,42 @@ function ProjectInfo({ data }: { data: ProjectDependency }) {
);
}
-function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) {
+type ValueLinkProps = {
+ value: string | undefined;
+ onClick?: () => void;
+ className?: string;
+ nullClassName?: string;
+};
+
+const ValueLink = ({ value, onClick, className, nullClassName }: ValueLinkProps) => {
+ return value ? (
+
+ {value}
+
+ ) : (
+
--
+ );
+};
+
+type NodeTooltipsProps = {
+ resourceId: number;
+ data: ModelDepsData | ProjectDependency | TrainDataset;
+ x: number;
+ y: number;
+ onMouseEnter?: () => void;
+ onMouseLeave?: () => void;
+ onVersionChange: (version: string) => void;
+};
+
+function NodeTooltips({
+ resourceId,
+ data,
+ x,
+ y,
+ onMouseEnter,
+ onMouseLeave,
+ onVersionChange,
+}: NodeTooltipsProps) {
if (!data) return null;
let Component = null;
const { model_type } = data;
@@ -129,7 +208,7 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr
model_type === NodeType.parent ||
model_type === NodeType.current
) {
- Component =
;
+ Component =
;
}
return (
(
undefined,
);
+ const [selectedMirror, setSelectedMirror] = useState
(
+ undefined,
+ ); // 选择的镜像,为了再次打开时恢复原来的选择
const { message } = App.useApp();
useEffect(() => {
@@ -84,16 +87,14 @@ function ModelDeploymentCreate() {
};
// 选择模型、镜像
- const selectResource = (name: string, selectType: string) => {
- let type;
+ const selectResource = (name: string, type: ResourceSelectorType) => {
let resource: ResourceSelectorResponse | undefined;
- switch (selectType) {
- case 'model':
- type = ResourceSelectorType.Model;
+ switch (type) {
+ case ResourceSelectorType.Model:
resource = selectedModel;
break;
default:
- type = ResourceSelectorType.Mirror;
+ resource = selectedMirror;
break;
}
const { close } = openAntdModal(ResourceSelectorModal, {
@@ -105,18 +106,20 @@ function ModelDeploymentCreate() {
if (res) {
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res.path);
+ setSelectedMirror(res);
} else {
- const response = res as ResourceSelectorResponse;
- const showValue = `${response.name}:${response.version}`;
+ const showValue = `${res.name}:${res.version}`;
form.setFieldValue(name, {
- ...pick(response, ['id', 'version', 'path']),
+ ...pick(res, ['id', 'version', 'path']),
showValue,
});
- setSelectedModel(response);
+ setSelectedModel(res);
}
} else {
if (type === ResourceSelectorType.Model) {
setSelectedModel(undefined);
+ } else {
+ setSelectedMirror(undefined);
}
form.setFieldValue(name, '');
}
@@ -248,7 +251,6 @@ function ModelDeploymentCreate() {
image={require('@/assets/img/model-deployment.png')}
style={{ marginTop: '20px', marginBottom: '24px' }}
>
-
selectResource('model', ResourceSelectorType.Model)}
/>
@@ -275,7 +278,7 @@ function ModelDeploymentCreate() {
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Model)}
- onClick={() => selectResource('model', 'model')}
+ onClick={() => selectResource('model', ResourceSelectorType.Model)}
>
选择模型
@@ -293,7 +296,12 @@ function ModelDeploymentCreate() {
},
]}
>
-
+ selectResource('image', ResourceSelectorType.Mirror)}
+ />
@@ -301,7 +309,7 @@ function ModelDeploymentCreate() {
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Mirror)}
- onClick={() => selectResource('image', 'image')}
+ onClick={() => selectResource('image', ResourceSelectorType.Mirror)}
>
选择镜像
diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less
index cd10e0d8..747d4da6 100644
--- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less
+++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less
@@ -11,7 +11,7 @@
:global {
.anticon.anticon-question-circle {
- margin-top: -14px;
+ margin-top: -12px;
}
}
}
diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
index 7336b333..7fe6440a 100644
--- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
+++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
@@ -76,9 +76,27 @@ const GlobalParamsDrawer = forwardRef(
{...restField}
name={[name, 'param_name']}
label="参数名称"
- rules={[{ required: true, message: '请输入参数名称' }]}
+ validateTrigger={[]}
+ rules={[
+ { required: true, message: '请输入参数名称' },
+ {
+ validator: (_, value) => {
+ const list = form.getFieldValue('global_param') || [];
+ const names = list.filter((item: any) => item?.param_name === value);
+ if (value && names.length > 1) {
+ return Promise.reject('参数名称不能重复');
+ } else {
+ return Promise.resolve();
+ }
+ },
+ },
+ ]}
>
-
+ form.validateFields()}
+ />
(null);
+ const config = selectorTypeConfig[type];
useEffect(() => {
setExpandedKeys([]);
@@ -143,9 +144,9 @@ function ResourceSelectorModal({
const params = {
page: 0,
size: 1000,
- [selectorTypeConfig[type].litReqParamKey]: available_range,
+ [config.litReqParamKey]: available_range,
};
- const getListReq = selectorTypeConfig[type].getList;
+ const getListReq = config.getList;
const [res] = await to(getListReq(params));
if (res) {
const list = res.data?.content || [];
@@ -161,10 +162,10 @@ function ResourceSelectorModal({
// 获取数据集\模型\镜像版本列表
const getVersions = async (parentId: number) => {
- const getVersionsReq = selectorTypeConfig[type].getVersions;
+ const getVersionsReq = config.getVersions;
const [res, error] = await to(getVersionsReq(parentId));
if (res) {
- const list = selectorTypeConfig[type].handleVersionResponse(res);
+ const list = config.handleVersionResponse(res);
const children = list.map(convertVersionToTreeData(parentId));
// 更新 treeData children
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
@@ -183,8 +184,8 @@ function ResourceSelectorModal({
// 获取版本下的文件
const getFiles = async (id: number, version: string) => {
- const getFilesReq = selectorTypeConfig[type].getFiles;
- const paramsKey = selectorTypeConfig[type].fileReqParamKey;
+ const getFilesReq = config.getFiles;
+ const paramsKey = config.fileReqParamKey;
const params = { version: version, [paramsKey]: id };
const [res] = await to(getFilesReq(params));
if (res) {
@@ -282,14 +283,12 @@ function ResourceSelectorModal({
}
};
- const title = `选择${selectorTypeConfig[type].name}`;
- const palceholder = `请输入${selectorTypeConfig[type].name}名称`;
+ const title = `选择${config.name}`;
+ const palceholder = `请输入${config.name}名称`;
const fileTitle =
- type === ResourceSelectorType.Mirror
- ? '已选镜像'
- : `已选${selectorTypeConfig[type].name}文件(${files.length})`;
- const tabItems = selectorTypeConfig[type].tabItems;
- const titleImg = selectorTypeConfig[type].modalIcon;
+ type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`;
+ const tabItems = config.tabItems;
+ const titleImg = config.modalIcon;
return (
diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx
index 4cfdcacb..c51ce935 100644
--- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx
+++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx
@@ -13,13 +13,12 @@ import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
import ModelMenu from '../components/ModelMenu';
import styles from './index.less';
import Props from './props';
-import { findAllParentNodes, findFirstDuplicate } from './utils';
+import { findAllParentNodes } from './utils';
let graph = null;
const EditPipeline = () => {
const navgite = useNavigate();
- let contextMenu = {};
const locationParams = useParams(); //新版本获取路由参数接口
const graphRef = useRef();
const paramsDrawerRef = useRef();
@@ -31,10 +30,23 @@ const EditPipeline = () => {
let dragSourceNode;
useEffect(() => {
- initMenu();
+ initGraph();
getFirstWorkflow(locationParams.id);
+
+ const changeSize = () => {
+ if (!graph || graph.get('destroyed')) return;
+ if (!graphRef.current) return;
+ graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight);
+ graph.fitView();
+ };
+
+ window.addEventListener('resize', changeSize);
+ return () => {
+ window.removeEventListener('resize', changeSize);
+ };
}, []);
+ // 拖拽结束,添加新节点
const onDragEnd = (val) => {
const { x, y } = val;
const point = graph.getPointByClient(x, y);
@@ -45,11 +57,14 @@ const EditPipeline = () => {
y: point.y,
id: val.component_name + '-' + s8(),
isCluster: false,
+ formError: true,
};
// console.log('model', model);
graph.addItem('node', model, false);
};
- const formChange = (val) => {
+
+ // 节点数据发生变化
+ const handleFormChange = (val) => {
if (graph) {
const data = graph.save();
const index = data.nodes.findIndex((item) => {
@@ -68,6 +83,8 @@ const EditPipeline = () => {
graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y);
}
};
+
+ // 保存
const savePipeline = async (val) => {
const [res, error] = await to(paramsDrawerRef.current.validateFields());
if (error) {
@@ -76,13 +93,6 @@ const EditPipeline = () => {
return;
}
- const duplicateName = findFirstDuplicate(res.global_param || []);
- if (duplicateName) {
- message.error('全局参数配置有重复的参数名称:' + duplicateName);
- openParamsDrawer();
- return;
- }
-
// const [propsRes, propsError] = await to(propsRef.current.getFieldsValue());
// if (propsError) {
// message.error('基本信息必填项需配置');
@@ -108,6 +118,8 @@ const EditPipeline = () => {
});
}, 500);
};
+
+ // 渲染数据
const getGraphData = (data) => {
if (graph) {
// 修改历史数据有蓝色边框的问题
@@ -122,6 +134,8 @@ const EditPipeline = () => {
}, 500);
}
};
+
+ // 处理并行边,暂时没有用
const processParallelEdgesOnAnchorPoint = (
edges,
offsetDiff = 15,
@@ -242,11 +256,11 @@ const EditPipeline = () => {
}
return false;
};
+
+ // 复制节点
const cloneElement = (item) => {
- console.log(item);
let data = graph.save();
const nodeId = s8();
- console.log(item.getModel());
data.nodes.push({
...item.getModel(),
label: item.getModel().label + '-copy',
@@ -256,66 +270,22 @@ const EditPipeline = () => {
});
graph.changeData(data);
};
- const getFirstWorkflow = (val) => {
- getWorkflowById(val).then((ret) => {
- if (ret && ret.data) {
- setGlobalParam(ret.data.global_param || []);
- }
- if (graph && ret.data && ret.data.dag) {
- getGraphData(JSON.parse(ret.data.dag));
- }
- });
- };
- // 上下文菜单
- const initMenu = () => {
- // const selectedNodes = this.selectedNodes;
- contextMenu = new G6.Menu({
- getContent(evt) {
- const type = evt.item.getType();
- const cloneDisplay = type === 'node' ? 'block' : 'none';
- return `
- `;
- },
- handleMenuClick: (target, item) => {
- switch (target.getAttribute('code')) {
- case 'delete':
- graph.removeItem(item);
- break;
- case 'clone':
- cloneElement(item);
- break;
- default:
- break;
- }
- },
- // offsetX and offsetY include the padding of the parent container
- // 需要加上父级容器的 padding-left 16 与自身偏移量 10
- offsetX: 16 + 10,
- // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
- offsetY: 0,
- // the types of items that allow the menu show up
- // 在哪些类型的元素上响应
- itemTypes: ['node', 'edge'],
- });
- initGraph();
+ // 获取流水线详情
+ const getFirstWorkflow = async (val) => {
+ const [res] = await to(getWorkflowById(val));
+ if (res && res.data) {
+ const { global_param, dag } = res.data;
+ setGlobalParam(global_param || []);
+ if (dag) {
+ getGraphData(JSON.parse(dag));
+ }
+ }
};
+ // 初始化图
const initGraph = () => {
+ const contextMenu = initMenu();
G6.registerNode(
'rect-node',
{
@@ -515,6 +485,7 @@ const EditPipeline = () => {
},
cursor: 'pointer',
lineWidth: 1,
+ lineAppendWidth: 4,
opacity: 1,
stroke: '#CDD0DC',
radius: 1,
@@ -527,19 +498,13 @@ const EditPipeline = () => {
},
},
},
- defaultCombo: {
- type: 'rect',
- fixCollapseSize: 70,
- style: {
- fill: '#00e0ff0d',
- stroke: '#00e0ff',
- lineDash: [5, 10],
- cursor: 'pointer',
- },
- },
});
+
+ bindEvents();
+ };
+
+ const bindEvents = () => {
graph.on('node:click', (e) => {
- e.stopPropagation();
if (e.target.get('name') !== 'anchor-point' && e.item) {
// 获取所有的上游节点
const parentNodes = findAllParentNodes(graph, e.item);
@@ -558,16 +523,6 @@ const EditPipeline = () => {
type:
targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal',
});
-
- // update the curveOffset for parallel edges
- // const edges = graph.save().edges;
- // processParallelEdgesOnAnchorPoint(edges);
- // graph.getEdges().forEach((edge, i) => {
- // graph.updateItem(edge, {
- // curveOffset: edges[i].curveOffset,
- // curvePosition: edges[i].curvePosition,
- // });
- // });
});
// 删除边时,修改 anchor-point 的 links 值
graph.on('afterremoveitem', (e) => {
@@ -639,13 +594,56 @@ const EditPipeline = () => {
graph.setItemState(e.item, 'drop', false);
dropAnchorIdx = undefined;
});
- window.onresize = () => {
- if (!graph || graph.get('destroyed')) return;
- if (!graphRef.current) return;
- graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight);
- graph.fitView();
- };
};
+
+ // 上下文菜单
+ const initMenu = () => {
+ const contextMenu = new G6.Menu({
+ getContent(evt) {
+ const type = evt.item.getType();
+ const cloneDisplay = type === 'node' ? 'block' : 'none';
+ return `
+ `;
+ },
+ handleMenuClick: (target, item) => {
+ switch (target.getAttribute('code')) {
+ case 'delete':
+ graph.removeItem(item);
+ break;
+ case 'clone':
+ cloneElement(item);
+ break;
+ default:
+ break;
+ }
+ },
+ // offsetX and offsetY include the padding of the parent container
+ // 需要加上父级容器的 padding-left 16 与自身偏移量 10
+ offsetX: 16 + 10,
+ // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
+ offsetY: 0,
+ // the types of items that allow the menu show up
+ // 在哪些类型的元素上响应
+ itemTypes: ['node', 'edge'],
+ });
+
+ return contextMenu;
+ };
+
return (
@@ -687,7 +685,7 @@ const EditPipeline = () => {
-
+