Browse Source

feat: 完成新建镜像

pull/28/head
cp3hnu 1 year ago
parent
commit
40c6b5ea00
19 changed files with 356 additions and 147 deletions
  1. +1
    -4
      react-ui/config/config.ts
  2. +5
    -7
      react-ui/src/app.tsx
  3. +5
    -0
      react-ui/src/components/CommonTableCell/index.tsx
  4. +13
    -0
      react-ui/src/components/DateTableCell/index.tsx
  5. +5
    -0
      react-ui/src/components/KFRadio/index.less
  6. +1
    -1
      react-ui/src/components/PageTitle/index.less
  7. +5
    -4
      react-ui/src/global.less
  8. +4
    -5
      react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx
  9. +1
    -1
      react-ui/src/pages/Mirror/create.less
  10. +107
    -32
      react-ui/src/pages/Mirror/create.tsx
  11. +1
    -1
      react-ui/src/pages/Mirror/info.less
  12. +84
    -20
      react-ui/src/pages/Mirror/info.tsx
  13. +4
    -5
      react-ui/src/pages/Mirror/list.less
  14. +85
    -23
      react-ui/src/pages/Mirror/list.tsx
  15. +1
    -1
      react-ui/src/pages/Pipeline/editPipeline/props.jsx
  16. +14
    -8
      react-ui/src/services/mirror/index.ts
  17. +0
    -34
      react-ui/src/utils/index.ts
  18. +2
    -0
      react-ui/src/utils/sessionKeys.ts
  19. +18
    -1
      react-ui/src/utils/ui.tsx

+ 1
- 4
react-ui/config/config.ts View File

@@ -1,14 +1,12 @@
// https://umijs.org/config/ // https://umijs.org/config/
import { defineConfig } from '@umijs/max'; import { defineConfig } from '@umijs/max';
import { join, resolve } from 'path';
import { join } from 'path';
import defaultSettings from './defaultSettings'; import defaultSettings from './defaultSettings';
import proxy from './proxy'; import proxy from './proxy';
import routes from './routes'; import routes from './routes';


const { REACT_APP_ENV = 'dev' } = process.env; const { REACT_APP_ENV = 'dev' } = process.env;


console.log('zzz', resolve(__dirname, '../src/styles/theme.less'));

export default defineConfig({ export default defineConfig({
/** /**
* @name 开启 hash 模式 * @name 开启 hash 模式
@@ -42,7 +40,6 @@ export default defineConfig({
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
// 只有设置为 variable, 才能使用 configProvide 动态设置主色调 // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
// 'root-entry-name': 'variable', // 'root-entry-name': 'variable',
'kf-success-color': '#ff0000',
}, },
/** /**
* @name moment 的国际化配置 * @name moment 的国际化配置


+ 5
- 7
react-ui/src/app.tsx View File

@@ -193,7 +193,6 @@ export const antd: RuntimeAntdConfig = (memo) => {
colorSuccess: themes['successColor'], colorSuccess: themes['successColor'],
colorError: themes['errorColor'], colorError: themes['errorColor'],
colorWarning: themes['warningColor'], colorWarning: themes['warningColor'],
// fontSize: themes['fontSize'],
}; };
memo.theme.components ??= {}; memo.theme.components ??= {};
memo.theme.components.Tabs = {}; memo.theme.components.Tabs = {};
@@ -207,20 +206,19 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultActiveBg: 'rgba(22, 100, 255, 0.12)', defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'], defaultActiveColor: themes['primaryColor'],
contentFontSize: themes['fontSize'],
paddingBlock: 4,
paddingInline: 15,
contentFontSize: parseInt(themes['fontSize']),
controlHeight: 34, controlHeight: 34,
}; };
memo.theme.components.Input = { memo.theme.components.Input = {
inputFontSize: themes['fontSize'],
paddingBlock: 4,
paddingInline: 15,
inputFontSize: parseInt(themes['fontSize']),
}; };
memo.theme.components.Table = { memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)', headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4, headerBorderRadius: 4,
}; };
memo.theme.components.Tabs = {
titleFontSize: 16,
};
memo.theme.cssVar = true; memo.theme.cssVar = true;
// memo.theme.hashed = false; // memo.theme.hashed = false;




+ 5
- 0
react-ui/src/components/CommonTableCell/index.tsx View File

@@ -0,0 +1,5 @@
function CommonTableCell(text?: string | null) {
return <span>{text ?? '--'}</span>;
}

export default CommonTableCell;

+ 13
- 0
react-ui/src/components/DateTableCell/index.tsx View File

@@ -0,0 +1,13 @@
import dayjs from 'dayjs';

function DateTableCell(text?: string | null) {
if (text === undefined || text === null || text === '') {
return <span>--</span>;
}
if (!dayjs(text).isValid()) {
return <span>日期无效</span>;
}
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
}

export default DateTableCell;

+ 5
- 0
react-ui/src/components/KFRadio/index.less View File

@@ -17,6 +17,11 @@
border: 1px solid @primary-color-hover; border: 1px solid @primary-color-hover;
} }


&:active {
color: @primary-color;
border: 1px solid @primary-color;
}

&--active { &--active {
color: @primary-color; color: @primary-color;
border: 1px solid @primary-color; border: 1px solid @primary-color;


+ 1
- 1
react-ui/src/components/PageTitle/index.less View File

@@ -1,7 +1,7 @@
.kf-page-title { .kf-page-title {
display: flex; display: flex;
align-items: center; align-items: center;
height: 49px;
height: 50px;
padding-left: 30px; padding-left: 30px;
background-image: url('../../assets/img/page-title-bg.png'); background-image: url('../../assets/img/page-title-bg.png');
} }

+ 5
- 4
react-ui/src/global.less View File

@@ -4,6 +4,7 @@ body,
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow-y: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'; 'Noto Color Emoji';
@@ -181,13 +182,13 @@ body {
margin-left: 20px; margin-left: 20px;
} }
.ant-pagination .ant-pagination-item-active a { .ant-pagination .ant-pagination-item-active a {
color: #fff;
// color: #fff;
border-radius: 6px; border-radius: 6px;
} }
.ant-pagination .ant-pagination-item-active:hover { .ant-pagination .ant-pagination-item-active:hover {
color: #fff;
background: rgba(22, 100, 255, 0.8);
border-color: rgba(22, 100, 255, 0.8);
// color: #fff;
// background: rgba(22, 100, 255, 0.8);
// border-color: rgba(22, 100, 255, 0.8);
border-radius: 6px; border-radius: 6px;
} }
.ant-pagination .ant-pagination-item { .ant-pagination .ant-pagination-item {


+ 4
- 5
react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx View File

@@ -6,10 +6,6 @@
import { MirrorVersionStatus } from '@/enums'; import { MirrorVersionStatus } from '@/enums';
import styles from './index.less'; import styles from './index.less';


type MirrorStatusCellProps = {
status: MirrorVersionStatus;
};

type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus;
type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys];


@@ -33,7 +29,10 @@ const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = {
}, },
}; };


function MirrorStatusCell({ status }: MirrorStatusCellProps) {
function MirrorStatusCell(status: MirrorVersionStatus) {
if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>;
}
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
} }




+ 1
- 1
react-ui/src/pages/Mirror/create.less View File

@@ -4,7 +4,7 @@
height: 100%; height: 100%;


&__content { &__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px; margin-top: 10px;
padding: 30px 30px 10px; padding: 30px 30px 10px;
overflow: auto; overflow: auto;


+ 107
- 32
react-ui/src/pages/Mirror/create.tsx View File

@@ -3,15 +3,20 @@
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 创建镜像 * @Description: 创建镜像
*/ */
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums'; import { CommonTabKeys } from '@/enums';
import { createPrivateMirrorReq, createPublicMirrorReq } from '@/services/mirror';
import { createMirrorReq } from '@/services/mirror';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { useNavigate, useSearchParams } from '@umijs/max';
import { Button, Col, Form, Input, Row, message } from 'antd';
import { mirrorNameKey } from '@/utils/sessionKeys';
import { getFileListFromEvent } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import styles from './create.less'; import styles from './create.less';


type FormData = { type FormData = {
@@ -19,7 +24,8 @@ type FormData = {
tag: string; tag: string;
description: string; description: string;
path?: string; path?: string;
type: string;
upload_type: string;
fileList?: UploadFile[];
}; };


const mirrorRadioItems: KFRadioItem[] = [ const mirrorRadioItems: KFRadioItem[] = [
@@ -37,15 +43,64 @@ const mirrorRadioItems: KFRadioItem[] = [


function MirrorCreate() { function MirrorCreate() {
const navgite = useNavigate(); const navgite = useNavigate();
const [seachParams] = useSearchParams();
const [form] = Form.useForm(); const [form] = Form.useForm();
const isPublic = seachParams.get('isPublic') === 'true';
const [nameDisabled, setNameDisabled] = useState(false);

const uploadProps: UploadProps = {
action: '/api/mmp/image/upload',
headers: {
Authorization: getAccessToken() || '',
},
maxCount: 1,
defaultFileList: [],
};

useEffect(() => {
const name = sessionStorage.getItem(mirrorNameKey);
if (name) {
form.setFieldValue('name', name);
setNameDisabled(true);
}
return () => {
sessionStorage.removeItem(mirrorNameKey);
};
}, []);


// 创建公网、本地镜像 // 创建公网、本地镜像
const createPublicMirror = async (params: FormData) => {
// createPrivateMirrorReq
const req = isPublic ? createPublicMirrorReq : createPrivateMirrorReq;
const [res] = await to(req(params));
const createPublicMirror = async (formData: FormData) => {
const upload_type = formData['upload_type'];
let params;
if (upload_type === CommonTabKeys.Public) {
params = {
...omit(formData, ['upload_type']),
upload_type: 0,
image_type: 0,
};
} else {
const fileList = formData['fileList'] ?? [];
if (fileList.length === 0) {
message.error('请上传文件');
return;
}
const file = fileList[0];
if (file.status === 'uploading') {
message.error('请等待文件上传完成');
return;
} else if (file.status === 'error') {
message.error('文件上传失败,请重新上传文件');
return;
}

params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
}

const [res] = await to(createMirrorReq(params));
if (res) { if (res) {
message.success('创建成功'); message.success('创建成功');
navgite(-1); navgite(-1);
@@ -54,13 +109,26 @@ function MirrorCreate() {


// 提交 // 提交
const handleSubmit = (values: FormData) => { const handleSubmit = (values: FormData) => {
console.log(values);
createPublicMirror(values); createPublicMirror(values);
}; };


// 取消
const cancel = () => {
navgite(-1);
};

const beforeUpload: UploadProps['beforeUpload'] = () => {
const fileList = form.getFieldValue('fileList');
if (fileList.length >= 1) {
message.error('只允许上传一个文件');
return Upload.LIST_IGNORE;
}
return true;
};

return ( return (
<div className={styles['mirror-create']}> <div className={styles['mirror-create']}>
<PageTitle title="基本信息"></PageTitle>
<PageTitle title="创建镜像"></PageTitle>
<div className={styles['mirror-create__content']}> <div className={styles['mirror-create__content']}>
<div> <div>
<Form <Form
@@ -69,7 +137,7 @@ function MirrorCreate() {
wrapperCol={{ flex: 1 }} wrapperCol={{ flex: 1 }}
labelAlign="left" labelAlign="left"
form={form} form={form}
initialValues={{ type: CommonTabKeys.Public }}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit} onFinish={handleSubmit}
> >
<SubAreaTitle <SubAreaTitle
@@ -89,13 +157,19 @@ function MirrorCreate() {
}, },
]} ]}
> >
<Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear />
<Input
placeholder="请输入镜像名称"
maxLength={64}
disabled={nameDisabled}
showCount
allowClear
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label=" " label=" "
name="tag"
name="tag_name"
labelCol={{ flex: '20px' }} labelCol={{ flex: '20px' }}
wrapperCol={{ flex: 1 }} wrapperCol={{ flex: 1 }}
required={false} required={false}
@@ -141,7 +215,7 @@ function MirrorCreate() {
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="构建方式" label="构建方式"
name="type"
name="upload_type"
rules={[ rules={[
{ {
required: true, required: true,
@@ -155,10 +229,12 @@ function MirrorCreate() {
</Row> </Row>
<Form.Item <Form.Item
noStyle noStyle
shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type}
shouldUpdate={(prevValues, curValues) =>
prevValues.upload_type !== curValues.upload_type
}
> >
{({ getFieldValue }) => { {({ getFieldValue }) => {
const type = getFieldValue('type');
const type = getFieldValue('upload_type');
if (type === CommonTabKeys.Public) { if (type === CommonTabKeys.Public) {
return ( return (
<> <>
@@ -199,7 +275,9 @@ function MirrorCreate() {
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="镜像文件" label="镜像文件"
name="path"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[ rules={[
{ {
required: true, required: true,
@@ -207,19 +285,11 @@ function MirrorCreate() {
}, },
]} ]}
> >
{/* <Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff' }} />}
>
上传文件
<Upload {...uploadProps} beforeUpload={beforeUpload}>
<Button type="link" style={{ paddingLeft: 0, paddingRight: 0 }}>
选择镜像文件
</Button> </Button>
</Upload> */}
</Upload>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@@ -233,7 +303,12 @@ function MirrorCreate() {
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
创建镜像 创建镜像
</Button> </Button>
<Button type="default" htmlType="reset" style={{ marginLeft: '20px' }}>
<Button
type="default"
htmlType="button"
onClick={cancel}
style={{ marginLeft: '20px' }}
>
取消 取消
</Button> </Button>
</Form.Item> </Form.Item>


+ 1
- 1
react-ui/src/pages/Mirror/info.less View File

@@ -23,7 +23,7 @@
} }


&__content { &__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px; margin-top: 10px;
padding: 30px 30px 0; padding: 30px 30px 0;
background-color: white; background-color: white;


+ 84
- 20
react-ui/src/pages/Mirror/info.tsx View File

@@ -3,16 +3,33 @@
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 镜像详情 * @Description: 镜像详情
*/ */
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { MirrorVersionStatus } from '@/enums';
import { useDomSize } from '@/hooks'; import { useDomSize } from '@/hooks';
import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror';
import {
deleteMirrorVersionReq,
getMirrorInfoReq,
getMirrorVersionListReq,
} from '@/services/mirror';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Col, ConfigProvider, Row, Table, TablePaginationConfig, TableProps } from 'antd';
import { mirrorNameKey } from '@/utils/sessionKeys';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useParams, useSearchParams } from '@umijs/max';
import {
Button,
Col,
ConfigProvider,
Flex,
Row,
Table,
message,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -27,6 +44,7 @@ type MirrorInfoData = {
}; };


type MirrorVersionData = { type MirrorVersionData = {
id: number;
version: string; version: string;
url: string; url: string;
status: string; status: string;
@@ -35,8 +53,9 @@ type MirrorVersionData = {
}; };


function MirrorInfo() { function MirrorInfo() {
const navigate = useNavigate();
const urlParams = useParams(); const urlParams = useParams();
const [seachParams] = useSearchParams();
const [searchParams] = useSearchParams();
const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({});
const [tableData, setTableData] = useState<MirrorVersionData[]>([]); const [tableData, setTableData] = useState<MirrorVersionData[]>([]);
const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]);
@@ -47,7 +66,7 @@ function MirrorInfo() {
current: 1, current: 1,
pageSize: 10, pageSize: 10,
}); });
const isPublic = seachParams.get('isPublic') === 'true';
const isPublic = searchParams.get('isPublic') === 'true';
useEffect(() => { useEffect(() => {
getMirrorInfo(); getMirrorInfo();
}, []); }, []);
@@ -61,7 +80,8 @@ function MirrorInfo() {
const [res] = await to(getMirrorInfoReq(id)); const [res] = await to(getMirrorInfoReq(id));
if (res && res.data) { if (res && res.data) {
const { name = '', description = '', version_count = '', create_time: time } = res.data; const { name = '', description = '', version_count = '', create_time: time } = res.data;
const create_time = time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '';
let create_time =
time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--';
setMirrorInfo({ setMirrorInfo({
name, name,
description, description,
@@ -87,6 +107,25 @@ function MirrorInfo() {
} }
}; };


// 删除镜像版本
const deleteMirrorVersion = async (id: number) => {
const [res] = await to(deleteMirrorVersionReq(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
...prev,
current: 1,
}));
} else {
getMirrorVersionList();
}
}
};

// 分页切换 // 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
if (action === 'paginate') { if (action === 'paginate') {
@@ -94,8 +133,21 @@ function MirrorInfo() {
} }
}; };


const downloadVersion = (record: MirrorVersionData) => {};
const removeVersion = (record: MirrorVersionData) => {};
// 处理删除
const handleVersionDelete = (record: MirrorVersionData) => {
modalConfirm({
title: '删除后,该镜像版本将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteMirrorVersion(record.id);
},
});
};

const createMirrorVersion = () => {
navigate(`/dataset/mirror/create`);
sessionStorage.setItem(mirrorNameKey, mirrorInfo.name || '');
};


const columns: TableProps<MirrorVersionData>['columns'] = [ const columns: TableProps<MirrorVersionData>['columns'] = [
{ {
@@ -103,31 +155,34 @@ function MirrorInfo() {
dataIndex: 'tag_name', dataIndex: 'tag_name',
key: 'tag_name', key: 'tag_name',
width: '25%', width: '25%',
render: CommonTableCell,
}, },
{ {
title: '镜像地址', title: '镜像地址',
dataIndex: 'url', dataIndex: 'url',
key: 'url', key: 'url',
render: CommonTableCell,
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 150, width: 150,
render: (text: string) => <MirrorStatusCell status={text as MirrorVersionStatus} />,
render: MirrorStatusCell,
}, },
{ {
title: '镜像大小', title: '镜像大小',
dataIndex: 'file_size', dataIndex: 'file_size',
key: 'file_size', key: 'file_size',
width: 150, width: 150,
render: CommonTableCell,
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'create_time', dataIndex: 'create_time',
key: 'create_time', key: 'create_time',
width: 200, width: 200,
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: DateTableCell,
}, },
{ {
title: '操作', title: '操作',
@@ -135,7 +190,7 @@ function MirrorInfo() {
width: 150, width: 150,
key: 'operation', key: 'operation',
hidden: isPublic, hidden: isPublic,
render: (_: any, record: any) => (
render: (_: any, record: MirrorVersionData) => (
<div> <div>
{!isPublic && ( {!isPublic && (
<ConfigProvider <ConfigProvider
@@ -150,7 +205,7 @@ function MirrorInfo() {
size="small" size="small"
key="remove" key="remove"
icon={<KFIcon type="icon-shanchu" />} icon={<KFIcon type="icon-shanchu" />}
onClick={() => removeVersion(record)}
onClick={() => handleVersionDelete(record)}
> >
删除 删除
</Button> </Button>
@@ -182,7 +237,7 @@ function MirrorInfo() {
<Col span={10}> <Col span={10}>
<div className={styles['mirror-info__basic__item']}> <div className={styles['mirror-info__basic__item']}>
<div className={styles['label']}>版本数:</div> <div className={styles['label']}>版本数:</div>
<div className={styles['value']}>{mirrorInfo.version_count || '--'}</div>
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div>
</div> </div>
</Col> </Col>
</Row> </Row>
@@ -201,12 +256,21 @@ function MirrorInfo() {
</Col> </Col>
</Row> </Row>
</div> </div>

<SubAreaTitle
title="镜像版本"
image={require('@/assets/img/mirror-version.png')}
style={{ marginTop: '40px' }}
></SubAreaTitle>
<Flex justify="space-between" align="center" style={{ marginTop: '40px' }}>
<SubAreaTitle
title="镜像版本"
image={require('@/assets/img/mirror-version.png')}
></SubAreaTitle>
{!isPublic && (
<Button
type="default"
onClick={createMirrorVersion}
icon={<KFIcon type="icon-xinjian2" />}
>
新增镜像版本
</Button>
)}
</Flex>
</div> </div>
<div <div
className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])}


+ 4
- 5
react-ui/src/pages/Mirror/list.less View File

@@ -1,13 +1,13 @@
.mirror-list { .mirror-list {
height: 100%; height: 100%;
&__tabs-container { &__tabs-container {
height: 49px;
height: 50px;
padding-left: 27px; padding-left: 27px;
background-image: url('../../assets/img/page-title-bg.png'); background-image: url('../../assets/img/page-title-bg.png');
} }


&__content { &__content {
height: calc(100% - 59px);
height: calc(100% - 60px);
margin-top: 10px; margin-top: 10px;
padding: 20px 30px 0; padding: 20px 30px 0;
background-color: white; background-color: white;
@@ -17,12 +17,11 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 10px;
} }


&__table { &__table {
height: calc(100% - 44px);
margin-top: 12px;
height: calc(100% - 34px - 28px);
margin-top: 28px;
} }
} }
} }

+ 85
- 23
react-ui/src/pages/Mirror/list.tsx View File

@@ -3,23 +3,28 @@
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 镜像列表 * @Description: 镜像列表
*/ */
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums'; import { CommonTabKeys } from '@/enums';
import { getMirrorListReq } from '@/services/mirror';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useSearchParams } from '@umijs/max';
import { import {
Button, Button,
ConfigProvider, ConfigProvider,
Input, Input,
Table, Table,
TablePaginationConfig,
TableProps,
Tabs, Tabs,
message,
type TablePaginationConfig,
type TableProps,
type TabsProps,
} from 'antd'; } from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames'; import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './list.less'; import styles from './list.less';


@@ -44,8 +49,12 @@ export type MirrorData = {
}; };


function MirrorList() { function MirrorList() {
const navgite = useNavigate();
const [activeTab, setActiveTab] = useState('Public');
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const isPrivate = searchParams.get('isPublic') === 'false';
const [activeTab, setActiveTab] = useState<string>(
isPrivate ? CommonTabKeys.Private : CommonTabKeys.Public,
);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [tableData, setTableData] = useState<MirrorData[]>([]); const [tableData, setTableData] = useState<MirrorData[]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
@@ -55,19 +64,37 @@ function MirrorList() {
current: 1, current: 1,
pageSize: 10, pageSize: 10,
}); });

useEffect(() => { useEffect(() => {
getMirrorList(); getMirrorList();
}, [activeTab, pagination]); }, [activeTab, pagination]);


// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setSearchText('');
setPagination({
showSizeChanger: true,
showQuickJumper: true,
current: 1,
pageSize: 10,
});
setTotal(0);
setTableData([]);
setActiveTab(value);
setSearchParams([['isPublic', value === CommonTabKeys.Public ? 'true' : 'false']], {
replace: true,
});
};
// 获取镜像列表 // 获取镜像列表
const getMirrorList = async () => {
const params = {
const getMirrorList = async (params?: Record<string, any>) => {
const reqParams = {
page: pagination.current! - 1, page: pagination.current! - 1,
size: pagination.pageSize, size: pagination.pageSize,
name: searchText, name: searchText,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0, image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
...params,
}; };
const [res] = await to(getMirrorListReq(params));
const [res] = await to(getMirrorListReq(reqParams));
if (res && res.data) { if (res && res.data) {
const { content = [], totalElements = 0 } = res.data; const { content = [], totalElements = 0 } = res.data;
setTableData(content); setTableData(content);
@@ -75,25 +102,57 @@ function MirrorList() {
} }
}; };


// 删除镜像
const deleteMirror = async (id: number) => {
const [res] = await to(deleteMirrorReq(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
...prev,
current: 1,
}));
} else {
getMirrorList();
}
}
};

// 搜索 // 搜索
const onSearch = () => {
getMirrorList();
const onSearch: SearchProps['onSearch'] = (value) => {
// 带参数是为了点清除时,searchText 更新不及时的问题
getMirrorList({
name: value,
});
}; };


// 查看详情 // 查看详情
const toDetail = (record: MirrorData) => { const toDetail = (record: MirrorData) => {
console.log('record', record); console.log('record', record);
navgite({
pathname: `/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`,
navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, {
state: {
isPublic: activeTab === CommonTabKeys.Public,
},
}); });
}; };


// 删除镜像
const deleteMirror = (record: MirrorData) => {};
// 处理删除
const handleMirrorDelete = (record: MirrorData) => {
modalConfirm({
title: '删除后,该镜像将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteMirror(record.id);
},
});
};


// 创建镜像 // 创建镜像
const createMirror = () => { const createMirror = () => {
navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` });
navigate(`/dataset/mirror/create`);
}; };


// 分页切换 // 分页切换
@@ -101,7 +160,7 @@ function MirrorList() {
if (action === 'paginate') { if (action === 'paginate') {
setPagination(pagination); setPagination(pagination);
} }
console.log(pagination, filters, sorter, action);
// console.log(pagination, filters, sorter, action);
}; };


const columns: TableProps<MirrorData>['columns'] = [ const columns: TableProps<MirrorData>['columns'] = [
@@ -110,32 +169,35 @@ function MirrorList() {
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '30%', width: '30%',
render: CommonTableCell,
}, },
{ {
title: '版本数据', title: '版本数据',
dataIndex: 'version_count', dataIndex: 'version_count',
key: 'version_count', key: 'version_count',
width: 100, width: 100,
render: CommonTableCell,
}, },
{ {
title: '镜像描述', title: '镜像描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
//ellipsis: true,
render: CommonTableCell,
ellipsis: true,
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'create_time', dataIndex: 'create_time',
key: 'create_time', key: 'create_time',
width: 200, width: 200,
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: DateTableCell,
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
width: activeTab === CommonTabKeys.Private ? 200 : 150, width: activeTab === CommonTabKeys.Private ? 200 : 150,
key: 'operation', key: 'operation',
render: (_: any, record: any) => (
render: (_: any, record: MirrorData) => (
<div> <div>
<Button <Button
type="link" type="link"
@@ -159,7 +221,7 @@ function MirrorList() {
size="small" size="small"
key="remove" key="remove"
icon={<KFIcon type="icon-shanchu" />} icon={<KFIcon type="icon-shanchu" />}
onClick={() => deleteMirror(record)}
onClick={() => handleMirrorDelete(record)}
> >
删除 删除
</Button> </Button>
@@ -176,7 +238,7 @@ function MirrorList() {
<Tabs <Tabs
activeKey={activeTab} activeKey={activeTab}
items={mirrorTabItems} items={mirrorTabItems}
onChange={setActiveTab}
onChange={hanleTabChange}
className={styles['model-tabs']} className={styles['model-tabs']}
/> />
</div> </div>


+ 1
- 1
react-ui/src/pages/Pipeline/editPipeline/props.jsx View File

@@ -1,9 +1,9 @@
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { getComputingResourceReq } from '@/services/pipeline'; import { getComputingResourceReq } from '@/services/pipeline';
import { pick } from '@/utils/index';
import { openAntdModal } from '@/utils/modal'; import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { Button, Drawer, Form, Input, Select } from 'antd'; import { Button, Drawer, Form, Input, Select } from 'antd';
import { pick } from 'lodash';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
import Styles from './editPipeline.less'; import Styles from './editPipeline.less';


+ 14
- 8
react-ui/src/services/mirror/index.ts View File

@@ -28,18 +28,24 @@ export function getMirrorVersionListReq(params: any) {
}); });
} }


// 创建公网镜像
export function createPublicMirrorReq(data: any) {
return request(`/api/mmp/image/net`, {
// 创建镜像
export function createMirrorReq(data: any) {
return request(`/api/mmp/image/addImageAndVersion`, {
method: 'POST', method: 'POST',
data, data,
}); });
} }


// 创建本地镜像
export function createPrivateMirrorReq(data: any) {
return request(`/api/mmp/image/local`, {
method: 'POST',
data,
// 删除镜像
export function deleteMirrorReq(id: number) {
return request(`/api/mmp/image/${id}`, {
method: 'DELETE',
});
}

// 删除镜像
export function deleteMirrorVersionReq(id: number) {
return request(`/api/mmp/imageVersion/${id}`, {
method: 'DELETE',
}); });
} }

+ 0
- 34
react-ui/src/utils/index.ts View File

@@ -14,37 +14,3 @@ export function getNameByCode(list: any[], code: any) {
}); });
return name; return name;
} }

/**
* Picks specified properties from an object and returns a new object with only those properties.
*
* @param obj - The object to pick properties from.
* @param properties - An array of property names to pick from the object.
* @return A new object with only the picked properties.
*/
export function pick<T extends object, K extends keyof T>(obj: T, properties: K[]): Pick<T, K> {
const picked: Partial<T> = {};
for (const key of properties) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
picked[key] = obj[key];
}
}
return picked as Pick<T, K>;
}

/**
* Omit properties from an object and return a new object without those properties.
*
* @param obj - The object to omit properties from.
* @param properties - An array of property names to omit from the object.
* @return A new object without the omitted properties.
*/
export function omit<T extends object, K extends keyof T>(obj: T, properties: K[]): Omit<T, K> {
const omitted: Partial<T> = { ...obj };
for (const key of properties) {
if (Object.prototype.hasOwnProperty.call(omitted, key)) {
delete omitted[key];
}
}
return omitted as Omit<T, K>;
}

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

@@ -0,0 +1,2 @@
// 用于新建镜像
export const mirrorNameKey = 'mirror-name';

+ 18
- 1
react-ui/src/utils/ui.tsx View File

@@ -4,7 +4,7 @@
* @Description: UI 公共方法 * @Description: UI 公共方法
*/ */
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { Modal, type ModalFuncProps } from 'antd';
import { Modal, type ModalFuncProps, type UploadFile } from 'antd';


// 自定义 Confirm 弹框 // 自定义 Confirm 弹框
export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) {
@@ -26,3 +26,20 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps)
onOk: onOk, onOk: onOk,
}); });
} }

// 从事件中获取上传文件列表,用于 Upload + Form 中
export const getFileListFromEvent = (e: any) => {
const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || [];
return fileList.map((item) => {
if (item.status === 'done') {
const { response } = item;
if (response?.code !== 200) {
return {
...item,
status: 'error',
};
}
}
return item;
});
};

Loading…
Cancel
Save