Unverified Commit 2cde200a authored by aiwenmo's avatar aiwenmo Committed by GitHub

[Feature-987][admin] ClusterConfig and jar add upload file (#988)

Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>
parent 4f04d9ad
......@@ -197,22 +197,9 @@
</dependency>
<!-- hadoop -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs-client</artifactId>
<version>3.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.3.2</version>
<scope>provided</scope>
<groupId>com.dlink</groupId>
<artifactId>dlink-client-hadoop</artifactId>
<scope>${scope.runtime}</scope>
</dependency>
</dependencies>
......
......@@ -45,7 +45,7 @@ public class UploadFileConstant {
// Upload file's dir constant----------------------------------------------------------------------------------------
static {
// Get admin jar's parent absolute path
DLINK_HOME_DIR = new ApplicationHome(UploadFileConstant.class).getSource().getParent();
DLINK_HOME_DIR = new ApplicationHome(UploadFileConstant.class).getSource().getParent() + "/../";
}
public static final String DLINK_HOME_DIR;
......
......@@ -27,7 +27,7 @@ import org.apache.commons.lang3.StringUtils;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
......@@ -55,7 +55,7 @@ public class FileUploadController {
* @param fileType Please refer {@link UploadFileConstant}, default is -1. If not provide, please provide the 'dir' value
* @return {@link Result}
*/
@PutMapping
@PostMapping
public Result upload(@RequestPart("files") MultipartFile[] files,
@RequestParam(value = "dir", defaultValue = "", required = false) String dir,
@RequestParam(value = "fileType", defaultValue = "-1", required = false) Byte fileType) {
......
......@@ -30,7 +30,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--<scope.type>provided</scope.type>-->
<hadoop.version>3.1.0</hadoop.version>
<hadoop.version>3.3.2</hadoop.version>
<scope.type>compile</scope.type>
</properties>
......
......@@ -709,7 +709,7 @@ CREATE TABLE `dlink_fragment` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='全局变量';
-- 0.7.7-SNAPSHOT 2022-08-22
-- 0.6.7-SNAPSHOT 2022-09-02
-- -----------------------
-- DROP TABLE IF EXISTS `dlink_upload_file_record`;
CREATE TABLE `dlink_upload_file_record` (
......
......@@ -17,15 +17,15 @@
*
*/
import React, {useState} from 'react';
import {Form, Button, Input, Modal, Select,Divider,Space,Switch} from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import type {ClusterConfigurationTableListItem} from "@/pages/ClusterConfiguration/data";
import {Button, Divider, Form, Input, message, Modal, Select, Space, Switch, Upload} from 'antd';
import {MinusCircleOutlined, PlusOutlined, UploadOutlined} from '@ant-design/icons';
import {getConfig, getConfigFormValues} from "@/pages/ClusterConfiguration/function";
import {FLINK_CONFIG_LIST, HADOOP_CONFIG_LIST, KUBERNETES_CONFIG_LIST} from "@/pages/ClusterConfiguration/conf";
import type {Config} from "@/pages/ClusterConfiguration/conf";
import {FLINK_CONFIG_LIST, HADOOP_CONFIG_LIST, KUBERNETES_CONFIG_LIST} from "@/pages/ClusterConfiguration/conf";
import {testClusterConfigurationConnect} from "@/pages/ClusterConfiguration/service";
import type {ClusterConfigurationTableListItem} from "@/pages/ClusterConfiguration/data";
import {CODE} from "@/components/Common/crud";
export type ClusterConfigurationFormProps = {
onCancel: (flag?: boolean) => void;
......@@ -47,12 +47,16 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
id: props.values.id,
name: props.values.name,
alias: props.values.alias,
type: props.values.type?props.values.type:"Yarn",
type: props.values.type ? props.values.type : "Yarn",
configJson: props.values.configJson,
note: props.values.note,
enabled: props.values.enabled,
});
const [hadoopConfigPath, setHadoopConfigPath] = useState<string>(getConfigFormValues(formVals)['hadoopConfigPath']);
const [flinkLibPath, setFlinkLibPath] = useState<string>(getConfigFormValues(formVals)['flinkLibPath']);
const [flinkConfigPath, setFlinkConfigPath] = useState<string>(getConfigFormValues(formVals)['flinkConfigPath']);
const {
onSubmit: handleSubmit,
onCancel: handleModalVisible,
......@@ -60,10 +64,13 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
} = props;
const onValuesChange = (change: any, all: any) => {
setFormVals({...formVals,...change});
setFormVals({...formVals, ...change});
setHadoopConfigPath(all['hadoopConfigPath']);
setFlinkLibPath(all['flinkLibPath']);
setFlinkConfigPath(all['flinkConfigPath']);
};
const buildConfig = (config: Config[]) =>{
const buildConfig = (config: Config[]) => {
const itemList: JSX.Element[] = [];
config.forEach(configItem => {
itemList.push(<Form.Item
......@@ -79,18 +86,44 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
const submitForm = async () => {
const fieldsValue = await form.validateFields();
const formValues = {
id:formVals.id,
name:fieldsValue.name,
alias:fieldsValue.alias,
type:fieldsValue.type,
note:fieldsValue.note,
enabled:fieldsValue.enabled,
configJson:JSON.stringify(getConfig(fieldsValue)),
id: formVals.id,
name: fieldsValue.name,
alias: fieldsValue.alias,
type: fieldsValue.type,
note: fieldsValue.note,
enabled: fieldsValue.enabled,
configJson: JSON.stringify(getConfig(fieldsValue)),
};
setFormVals(formValues);
handleSubmit(formValues);
};
const getUploadProps = (dir: string) => {
return {
name: 'files',
action: '/api/fileUpload',
// accept: 'application/json',
headers: {
authorization: 'authorization-text',
},
data: {
dir
},
showUploadList: true,
onChange(info) {
if (info.file.status === 'done') {
if (info.file.response.code == CODE.SUCCESS) {
message.success(info.file.response.msg);
} else {
message.warn(info.file.response.msg);
}
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
},
}
};
const renderContent = (formValsPara: Partial<ClusterConfigurationTableListItem>) => {
return (
<>
......@@ -103,82 +136,87 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
<Option value="Kubernetes">Flink On Kubernetes</Option>
</Select>
</Form.Item>
{formValsPara.type=='Yarn'?<>
<Divider>Hadoop 配置</Divider>
<Form.Item
name="hadoopConfigPath"
label="配置文件路径"
rules={[{required: true, message: '请输入 hadoop 配置文件路径!'}]}
help="指定配置文件路径(末尾无/),需要包含以下文件:core-site.xml,hdfs-site.xml,yarn-site.xml"
>
<Input placeholder="值如 /etc/hadoop/conf"/>
</Form.Item>
<Divider orientation="left" plain>自定义配置(高优先级)</Divider>
{buildConfig(HADOOP_CONFIG_LIST)}
<Form.Item
label="其他配置"
>
<Form.List name="hadoopConfigList">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex' }} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
fieldKey={[fieldKey, 'name']}
>
<Input placeholder="name" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
>
<Input placeholder="value" />
{formValsPara.type == 'Yarn' ? <>
<Divider>Hadoop 配置</Divider>
<Form.Item
name="hadoopConfigPath"
label="配置文件路径"
rules={[{required: true, message: '请输入 hadoop 配置文件路径!'}]}
help="指定配置文件路径(末尾无/),需要包含以下文件:core-site.xml,hdfs-site.xml,yarn-site.xml"
>
<Input placeholder="值如 /etc/hadoop/conf" addonAfter={
<Form.Item name="suffix" noStyle>
<Upload {...getUploadProps(hadoopConfigPath)}>
<UploadOutlined/>
</Upload>
</Form.Item>}/>
</Form.Item>
<Divider orientation="left" plain>自定义配置(高优先级)</Divider>
{buildConfig(HADOOP_CONFIG_LIST)}
<Form.Item
label="其他配置"
>
<Form.List name="hadoopConfigList">
{(fields, {add, remove}) => (
<>
{fields.map(({key, name, fieldKey, ...restField}) => (
<Space key={key} style={{display: 'flex'}} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
fieldKey={[fieldKey, 'name']}
>
<Input placeholder="name"/>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
>
<Input placeholder="value"/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)}/>
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined/>}>
添加一个自定义项
</Button>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加一个自定义项
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item></>:undefined}
{formValsPara.type=='Kubernetes'?<>
</>
)}
</Form.List>
</Form.Item></> : undefined}
{formValsPara.type == 'Kubernetes' ? <>
<Divider>Kubernetes 配置</Divider>
{buildConfig(KUBERNETES_CONFIG_LIST)}
<Form.Item
label="其他配置"
>
<Form.List name="kubernetesConfigList">
{(fields, { add, remove }) => (
{(fields, {add, remove}) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex' }} align="baseline">
{fields.map(({key, name, fieldKey, ...restField}) => (
<Space key={key} style={{display: 'flex'}} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
fieldKey={[fieldKey, 'name']}
>
<Input placeholder="name" />
<Input placeholder="name"/>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
>
<Input placeholder="value" />
<Input placeholder="value"/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
<MinusCircleOutlined onClick={() => remove(name)}/>
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined/>}>
添加一个自定义项
</Button>
</Form.Item>
......@@ -186,61 +224,71 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
)}
</Form.List>
</Form.Item>
</>:undefined}
</> : undefined}
<Divider>Flink 配置</Divider>
{formValsPara.type=='Yarn'?<>
<Form.Item
name="flinkLibPath"
label="lib 路径"
rules={[{required: true, message: '请输入 lib 的 hdfs 路径!'}]}
help="指定 lib 的 hdfs 路径(末尾无/),需要包含 Flink 运行时的依赖"
>
<Input placeholder="值如 hdfs:///flink/lib"/>
</Form.Item>
</>:undefined}
{formValsPara.type == 'Yarn' ? <>
<Form.Item
name="flinkLibPath"
label="lib 路径"
rules={[{required: true, message: '请输入 lib 的 hdfs 路径!'}]}
help="指定 lib 的 hdfs 路径(末尾无/),需要包含 Flink 运行时的依赖"
>
<Input placeholder="值如 hdfs:///flink/lib" addonAfter={
<Form.Item name="suffix" noStyle>
<Upload {...getUploadProps(flinkLibPath)}>
<UploadOutlined/>
</Upload>
</Form.Item>}/>
</Form.Item>
</> : undefined}
<Form.Item
name="flinkConfigPath"
label="配置文件路径"
rules={[{required: true, message: '请输入 flink-conf.yaml 路径!'}]}
help="指定 flink-conf.yaml 的路径(末尾无/)"
>
<Input placeholder="值如 /opt/module/flink/conf"/>
<Input placeholder="值如 /opt/module/flink/conf" addonAfter={
<Form.Item name="suffix" noStyle>
<Upload {...getUploadProps(flinkConfigPath)}>
<UploadOutlined/>
</Upload>
</Form.Item>}/>
</Form.Item>
<Divider orientation="left" plain>自定义配置(高优先级)</Divider>
{buildConfig(FLINK_CONFIG_LIST)}
<Form.Item
label="其他配置"
>
<Form.List name="flinkConfigList">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
fieldKey={[fieldKey, 'name']}
>
<Input placeholder="name" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
>
<Input placeholder="value" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加一个自定义项
</Button>
</Form.Item>
</>
)}
</Form.List>
<Form.List name="flinkConfigList">
{(fields, {add, remove}) => (
<>
{fields.map(({key, name, fieldKey, ...restField}) => (
<Space key={key} style={{display: 'flex', marginBottom: 8}} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
fieldKey={[fieldKey, 'name']}
>
<Input placeholder="name"/>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
>
<Input placeholder="value"/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)}/>
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined/>}>
添加一个自定义项
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
<Divider>基本配置</Divider>
<Form.Item
......@@ -272,16 +320,16 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
);
};
const testForm = async ()=>{
const testForm = async () => {
const fieldsValue = await form.validateFields();
const formValues = {
id :formVals.id,
name:fieldsValue.name,
alias:fieldsValue.alias,
type:fieldsValue.type,
note:fieldsValue.note,
enabled:fieldsValue.enabled,
configJson:JSON.stringify(getConfig(fieldsValue)),
id: formVals.id,
name: fieldsValue.name,
alias: fieldsValue.alias,
type: fieldsValue.type,
note: fieldsValue.note,
enabled: fieldsValue.enabled,
configJson: JSON.stringify(getConfig(fieldsValue)),
} as ClusterConfigurationTableListItem;
setFormVals(formValues);
testClusterConfigurationConnect(formValues);
......@@ -306,7 +354,7 @@ const ClusterConfigurationForm: React.FC<ClusterConfigurationFormProps> = (props
width={1200}
bodyStyle={{padding: '32px 40px 48px'}}
destroyOnClose
title={formVals.id?"维护集群配置":"创建集群配置"}
title={formVals.id ? "维护集群配置" : "创建集群配置"}
visible={modalVisible}
footer={renderFooter()}
onCancel={() => handleModalVisible()}
......
......@@ -19,14 +19,13 @@
import React, {useRef, useState} from "react";
import {DownOutlined, HeartOutlined, PlusOutlined, UserOutlined} from '@ant-design/icons';
import {ActionType, ProColumns} from "@ant-design/pro-table";
import {Button, message, Input, Drawer, Modal, Dropdown, Menu} from 'antd';
import {PageContainer, FooterToolbar} from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import {DownOutlined, PlusOutlined} from '@ant-design/icons';
import ProTable, {ActionType, ProColumns} from "@ant-design/pro-table";
import {Button, Drawer, Dropdown, Menu, message, Modal, Upload} from 'antd';
import {FooterToolbar, PageContainer} from '@ant-design/pro-layout';
import ProDescriptions from '@ant-design/pro-descriptions';
import {JarTableListItem} from "@/pages/Jar/data";
import {handleAddOrUpdate, handleRemove, queryData, updateEnabled} from "@/components/Common/crud";
import {CODE, handleAddOrUpdate, handleRemove, queryData, updateEnabled} from "@/components/Common/crud";
import JarForm from "@/pages/Jar/components/JarForm";
const url = '/api/jar';
......@@ -57,6 +56,32 @@ const JarTableList: React.FC<{}> = (props: any) => {
}
};
const getUploadProps = (dir: string) => {
return {
name: 'files',
action: '/api/fileUpload',
accept: 'jar',
headers: {
authorization: 'authorization-text',
},
data: {
dir
},
showUploadList: true,
onChange(info) {
if (info.file.status === 'done') {
if (info.file.response.code == CODE.SUCCESS) {
message.success(info.file.response.msg);
} else {
message.warn(info.file.response.msg);
}
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
},
}
};
const MoreBtn: React.FC<{
item: JarTableListItem;
}> = ({item}) => (
......@@ -132,7 +157,7 @@ const JarTableList: React.FC<{}> = (props: any) => {
title: '启动类',
sorter: true,
dataIndex: 'mainClass',
},{
}, {
title: '执行参数',
sorter: true,
dataIndex: 'paras',
......@@ -194,6 +219,9 @@ const JarTableList: React.FC<{}> = (props: any) => {
>
配置
</a>,
<Upload {...getUploadProps(record.path)}>
<a>上传</a>
</Upload>,
<MoreBtn key="more" item={record}/>,
],
},
......@@ -206,84 +234,102 @@ const JarTableList: React.FC<{}> = (props: any) => {
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
labelWidth: 120,
}}
toolBarRender={() => [
<Button type="primary" onClick={() => handleModalVisible(true)}>
<PlusOutlined/> 新建
</Button>,
]}
<Button type="primary" onClick={() => handleModalVisible(true)}>
<PlusOutlined/> 新建
</Button>,
]}
request={(params, sorter, filter) => queryData(url, {...params, sorter, filter})}
columns={columns}
rowSelection={{
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
}}
/>
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
已选择 <a style={{fontWeight: 600}}>{selectedRowsState.length}</a>&nbsp;&nbsp;
<span>
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
}}
/>
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
已选择 <a style={{fontWeight: 600}}>{selectedRowsState.length}</a>&nbsp;&nbsp;
<span>
被禁用的集群配置共 {selectedRowsState.length - selectedRowsState.reduce((pre, item) => pre + (item.enabled ? 1 : 0), 0)}
</span>
</div>
}
</div>
}
>
<Button type="primary" danger
onClick={() => {
Modal.confirm({
title: '删除Jar配置',
content: '确定删除选中的Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await handleRemove(url, selectedRowsState);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}
});
}}
>
<Button type="primary" danger
onClick={() => {
Modal.confirm({
title: '删除Jar配置',
content: '确定删除选中的Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await handleRemove(url, selectedRowsState);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}
});
}}
>
批量删除
</Button>
<Button type="primary"
onClick={() => {
Modal.confirm({
title: '启用Jar配置',
content: '确定启用选中的Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await updateEnabled(url, selectedRowsState, true);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}
});
}}
>批量启用</Button>
<Button danger
onClick={() => {
Modal.confirm({
title: '禁用Jar配置',
content: '确定禁用选中的Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await updateEnabled(url, selectedRowsState, false);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}
});
}}
>批量禁用</Button>
</FooterToolbar>
)}
批量删除
</Button>
<Button type="primary"
onClick={() => {
Modal.confirm({
title: '启用Jar配置',
content: '确定启用选中的Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await updateEnabled(url, selectedRowsState, true);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}
});
}}
>批量启用</Button>
<Button danger
onClick={() => {
Modal.confirm({
title: '禁用Jar配置',
content: '确定禁用选中的Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await updateEnabled(url, selectedRowsState, false);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}
});
}}
>批量禁用</Button>
</FooterToolbar>
)}
<JarForm
onSubmit={async (value) => {
const success = await handleAddOrUpdate(url, value);
if (success) {
handleModalVisible(false);
setFormValues({});
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleModalVisible(false);
}}
modalVisible={modalVisible}
values={{}}
/>
{formValues && Object.keys(formValues).length ? (
<JarForm
onSubmit={async (value) => {
const success = await handleAddOrUpdate(url, value);
if (success) {
handleModalVisible(false);
handleUpdateModalVisible(false);
setFormValues({});
if (actionRef.current) {
actionRef.current.reload();
......@@ -291,55 +337,37 @@ const JarTableList: React.FC<{}> = (props: any) => {
}
}}
onCancel={() => {
handleModalVisible(false);
handleUpdateModalVisible(false);
setFormValues({});
}}
modalVisible={modalVisible}
values={{}}
modalVisible={updateModalVisible}
values={formValues}
/>
{formValues && Object.keys(formValues).length ? (
<JarForm
onSubmit={async (value) => {
const success = await handleAddOrUpdate(url, value);
if (success) {
handleUpdateModalVisible(false);
setFormValues({});
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleUpdateModalVisible(false);
setFormValues({});
}}
modalVisible={updateModalVisible}
values={formValues}
/>
): null}
<Drawer
width={600}
visible={!!row}
onClose={() => {
setRow(undefined);
}}
closable={false}
>
{row?.name && (
<ProDescriptions<JarTableListItem>
column={2}
title={row?.name}
request={async () => ({
) : null}
<Drawer
width={600}
visible={!!row}
onClose={() => {
setRow(undefined);
}}
closable={false}
>
{row?.name && (
<ProDescriptions<JarTableListItem>
column={2}
title={row?.name}
request={async () => ({
data: row || {},
})}
params={{
params={{
id: row?.name,
}}
columns={columns}
/>
)}
</Drawer>
columns={columns}
/>
)}
</Drawer>
</PageContainer>
);
);
};
export default JarTableList;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment