Merge pull request '合并dev' (#125) from dev into master

This commit is contained in:
cp3hnu 2024-09-02 08:41:02 +08:00
commit 8860982ad2
13 changed files with 642 additions and 3 deletions

View File

@ -198,6 +198,17 @@ export default [
},
],
},
{
name: '代码配置',
path: 'codeConfig',
routes: [
{
name: '代码配置',
path: '',
component: './CodeConfig/List',
},
],
},
],
},
{

View File

@ -10,6 +10,12 @@ export enum CommonTabKeys {
Public = 'Public', // 公开
}
// 公开还是私有
export enum AvailableRange {
Public = 1, // 公开
Private = 0, // 私有
}
// 实验状态
export enum ExperimentStatus {
Running = 'Running', // 运行中

View File

@ -0,0 +1,47 @@
.code-config-list {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
height: 100%;
padding: 20px 0;
background: white;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
padding: 0 30px;
color: @text-color;
font-size: 15px;
}
&__content {
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 20px;
align-content: flex-start;
width: 100%;
margin-bottom: 30px;
padding: 0 30px;
overflow-y: auto;
}
&__empty {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
}
:global {
.ant-pagination {
margin-right: 30px;
text-align: right;
}
}
}

View File

@ -0,0 +1,173 @@
import KFIcon from '@/components/KFIcon';
import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { App, Button, Empty, Input, Pagination, PaginationProps } from 'antd';
import { useEffect, useState } from 'react';
import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal';
import CodeConfigItem from '../components/CodeConfigItem';
import styles from './index.less';
// 代码配置数据
export type CodeConfigData = {
id: number;
code_repo_name: string;
code_repo_vis: number;
git_url: string;
git_branch: string;
git_user_name: string;
git_password: string;
ssh_key: string;
verify_mode: number;
create_by: string;
create_time: string;
update_by: string;
update_time: string;
};
export type ResourceListRef = {
reset: () => void;
};
function CodeConfigList() {
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>({
current: 1,
pageSize: 20,
});
const [searchText, setSearchText] = useState<string | undefined>(undefined);
const [inputText, setInputText] = useState<string | undefined>(undefined);
const { message } = App.useApp();
useEffect(() => {
getDataList();
}, [pagination, searchText]);
// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText !== '' ? searchText : undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};
// 删除请求
const deleteRecord = async (id: number) => {
const [res] = await to(deleteCodeConfigReq(id));
if (res) {
getDataList();
message.success('删除成功');
}
};
// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
};
// 删除
const handleRemove = (record: CodeConfigData) => {
modalConfirm({
title: '确定删除这个代码配置吗?',
onOk: () => {
deleteRecord(record.id);
},
});
};
// 修改
const handleClick = (record: CodeConfigData) => {
const { close } = openAntdModal(AddCodeConfigModal, {
opType: OperationType.Update,
codeConfigData: record,
onOk: () => {
getDataList();
close();
},
});
};
// 新建
const createCodeConfig = () => {
const { close } = openAntdModal(AddCodeConfigModal, {
opType: OperationType.Create,
onOk: () => {
getDataList();
close();
},
});
};
// 分页切换
const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
setPagination({
current: page,
pageSize: pageSize,
});
};
return (
<div className={styles['code-config-list']}>
<div className={styles['code-config-list__header']}>
<span>{total}</span>
<div>
<Input.Search
placeholder="按代码仓库名称筛选"
allowClear
onSearch={handleSearch}
style={{
width: 300,
}}
onChange={(e) => setInputText(e.target.value)}
value={inputText}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={createCodeConfig}
icon={<KFIcon type="icon-xinjian2" />}
>
</Button>
</div>
</div>
{dataList?.length !== 0 ? (
<>
<div className={styles['code-config-list__content']}>
{dataList?.map((item) => (
<CodeConfigItem
item={item}
key={item.id}
onRemove={handleRemove}
onClick={handleClick}
/>
))}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={handlePageChange}
{...pagination}
/>
</>
) : (
<div className={styles['code-config-list__empty']}>
<Empty></Empty>
</div>
)}
</div>
);
}
export default CodeConfigList;

View File

@ -0,0 +1,244 @@
import KFModal from '@/components/KFModal';
import { AvailableRange } from '@/enums';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { addCodeConfigReq, updateCodeConfigReq } from '@/services/codeConfig';
import { to } from '@/utils/promise';
import { Form, Input, Radio, message, type ModalProps } from 'antd';
import { omit } from 'lodash';
export enum VerifyMode {
Password = 0, // 用户名密码
SSH = 1, // SSH Key
}
export enum OperationType {
Create = 0, // 新建
Update = 1, // 更新
}
type FormData = Partial<CodeConfigData>;
interface AddCodeConfigModalProps extends Omit<ModalProps, 'onOk'> {
opType: OperationType;
codeConfigData?: CodeConfigData;
onOk: () => void;
}
function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeConfigModalProps) {
// 上传请求
const createCodeConfig = async (formData: FormData) => {
const params: FormData & { id?: number } = {
...formData,
};
// 清除多余的信息
if (formData.code_repo_vis === AvailableRange.Public) {
omit(params, ['verify_mode', 'git_user_name', 'git_password', 'ssh_key']);
}
if (formData.verify_mode === VerifyMode.Password) {
omit(params, ['ssh_key']);
} else if (formData.verify_mode === VerifyMode.SSH) {
omit(params, ['git_user_name', 'git_password']);
}
if (opType === OperationType.Update) {
params.id = codeConfigData?.id;
}
const request = opType === OperationType.Create ? addCodeConfigReq : updateCodeConfigReq;
const [res] = await to(request(params));
if (res) {
message.success(opType === OperationType.Create ? '创建成功' : '修改成功');
onOk?.();
}
};
// 提交
const onFinish = (formData: FormData) => {
createCodeConfig(formData);
};
// 设置初始值
const initialValues: FormData = codeConfigData ?? {
code_repo_vis: AvailableRange.Public,
verify_mode: VerifyMode.Password,
};
if (initialValues.verify_mode === undefined || initialValues.verify_mode === null) {
initialValues.verify_mode = VerifyMode.Password;
}
return (
<KFModal
{...rest}
title="新建代码配置"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
destroyOnClose
>
<Form
name="form"
layout="vertical"
onFinish={onFinish}
initialValues={initialValues}
autoComplete="off"
>
{/* 禁止 Chrome 自动填充 */}
{/* <Input type="text" style={{ display: 'none' }} />
<Input type="password" style={{ display: 'none' }} /> */}
<Form.Item
label="代码仓库名称"
name="code_repo_name"
required
rules={[
{
required: true,
message: '请输入代码仓库名称',
},
]}
>
<Input placeholder="请输入代码仓库名称" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="代码仓库可见性"
name="code_repo_vis"
rules={[
{
required: true,
message: '请选择代码仓库可见性',
},
]}
>
<Radio.Group>
<Radio value={AvailableRange.Public}></Radio>
<Radio value={AvailableRange.Private}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="Git 地址"
name="git_url"
rules={[
{
required: true,
message: '请输入 Git 地址',
},
{
type: 'url',
message: '请输入正确的 Git 地址',
},
]}
>
<Input placeholder="请输入 Git 地址" showCount allowClear maxLength={256} />
</Form.Item>
<Form.Item
label="代码分支/Tag"
name="git_branch"
rules={[
{
required: true,
message: '请输入代码分支/Tag',
},
]}
>
<Input placeholder="请输入代码分支/Tag" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues?.code_repo_vis !== currentValues?.code_repo_vis
}
>
{({ getFieldValue }) => {
return getFieldValue('code_repo_vis') === AvailableRange.Private ? (
<>
<Form.Item
label="验证方式"
name="verify_mode"
rules={[
{
required: true,
message: '请选择验证方式',
},
]}
>
<Radio.Group>
<Radio value={VerifyMode.Password}>/</Radio>
<Radio value={VerifyMode.SSH}>SSH Key</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues?.verify_mode !== currentValues?.verify_mode
}
>
{({ getFieldValue }) => {
return getFieldValue('verify_mode') === VerifyMode.Password ? (
<>
<Form.Item
label="Git 用户名"
name="git_user_name"
rules={[
{
required: true,
message: '请输入 Git 用户名',
},
]}
>
<Input
placeholder="请输入 Git 用户名"
autoComplete="off"
showCount
allowClear
maxLength={64}
/>
</Form.Item>
<Form.Item
label="Git 密码"
name="git_password"
rules={[
{
required: true,
message: '请输入 Git 密码',
},
]}
>
<Input.Password
autoComplete="new-password"
placeholder="请输入 Git 密码"
allowClear
/>
</Form.Item>
</>
) : (
<Form.Item
label="SSH Key"
name="ssh_key"
rules={[
{
required: true,
message: '请输入 SSH Key',
},
]}
>
<Input.TextArea
placeholder="请输入 SSH Key"
showCount
maxLength={1024}
autoSize={{ minRows: 3, maxRows: 6 }}
allowClear
/>
</Form.Item>
);
}}
</Form.Item>
</>
) : null;
}}
</Form.Item>
</Form>
</KFModal>
);
}
export default AddCodeConfigModal;

View File

@ -0,0 +1,68 @@
.code-config-item {
position: relative;
width: calc(25% - 15px);
padding: 20px;
background: white;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;
@media screen and (max-width: 1860px) {
& {
width: calc(33.33% - 13.33px);
}
}
&__name {
position: relative;
display: inline-block;
height: 24px;
margin: 0 10px 0 0 !important;
color: @text-color;
font-size: 16px;
}
&__url {
margin-bottom: 10px;
color: @text-color-secondary;
font-size: 14px;
}
&__description {
height: 44px;
margin-bottom: 20px;
color: @text-color-secondary;
font-size: 14px;
.multiLine(2);
}
&__time {
display: flex;
flex: 0 1 content;
align-items: center;
width: 100%;
color: #808080;
font-size: 13px;
}
&:hover {
border-color: @primary-color;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
.resource-item__name {
color: @primary-color;
}
}
}
.resource-item__name {
&::after {
position: absolute;
top: 14px;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%);
content: '';
}
}

View File

@ -0,0 +1,51 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import KFIcon from '@/components/KFIcon';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { formatDate } from '@/utils/date';
import { Button, Flex, Typography } from 'antd';
import styles from './index.less';
type CodeConfigItemProps = {
item: CodeConfigData;
onClick: (item: CodeConfigData) => void;
onRemove: (item: CodeConfigData) => void;
};
function CodeConfigItem({ item, onClick, onRemove }: CodeConfigItemProps) {
return (
<div className={styles['code-config-item']} onClick={() => onClick(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}>
<Typography.Paragraph
className={styles['code-config-item__name']}
ellipsis={{ tooltip: item.code_repo_name }}
>
{item.code_repo_name}
</Typography.Paragraph>
<Button
type="text"
shape="circle"
onClick={(e) => {
e.stopPropagation();
onRemove(item);
}}
>
<KFIcon type="icon-shanchu" font={17} />
</Button>
</Flex>
<div className={styles['code-config-item__description']}>{item.git_url}</div>
<Flex justify="space-between">
<div className={styles['code-config-item__time']}>
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
<span>{item.create_by}</span>
</div>
<div className={styles['code-config-item__time']}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
</div>
</Flex>
</div>
);
}
export default CodeConfigItem;

View File

@ -9,7 +9,7 @@ import { App, Button, Input, Pagination, PaginationProps } from 'antd';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config';
import AddDatasetModal from '../AddDatasetModal';
import ResourceItem from '../Resourcetem';
import ResourceItem from '../ResourceItem';
import styles from './index.less';
export type ResourceListRef = {
@ -161,7 +161,7 @@ function ResourceList(
<span>{total}</span>
<div>
<Input.Search
placeholder="按数据名称筛选"
placeholder={`${config.name}名称筛选`}
allowClear
onSearch={handleSearch}
style={{

View File

@ -166,7 +166,7 @@ function EditorList() {
key: 'name',
width: '30%',
render: (text, record) =>
record.url ? (
record.url && record.status === DevEditorStatus.Running ? (
<a className="kf-table-row-link" onClick={(e) => gotoEditorPage(e, record)}>
{text}
</a>

View File

@ -0,0 +1,39 @@
import { request } from '@umijs/max';
// 分页查询代码配置
export function getCodeConfigListReq(params) {
return request(`/api/mmp/codeConfig`, {
method: 'GET',
params,
});
}
// 新增代码配置
export function addCodeConfigReq(data) {
return request(`/api/mmp/codeConfig`, {
method: 'POST',
data,
});
}
// 更新代码配置
export function updateCodeConfigReq(data) {
return request(`/api/mmp/codeConfig`, {
method: 'PUT',
data,
});
}
// 删除代码配置
export function deleteCodeConfigReq(id) {
return request(`/api/mmp/codeConfig/${id}`, {
method: 'DELETE',
});
}
// 查询代码配置详情
export function getCodeConfigDetailReq(id) {
return request(`/api/mmp/codeConfig/${id}`, {
method: 'GET'
});
}