Commit ce40970f authored by godkaikai's avatar godkaikai

Jar 配置管理

parent 6bd960ef
package com.dlink.controller;
import com.dlink.common.result.ProTableResult;
import com.dlink.common.result.Result;
import com.dlink.model.Jar;
import com.dlink.service.JarService;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* JarController
*
* @author wenmo
* @since 2021/11/13
**/
@Slf4j
@RestController
@RequestMapping("/api/jar")
public class JarController {
@Autowired
private JarService jarService;
/**
* 新增或者更新
*/
@PutMapping
public Result saveOrUpdate(@RequestBody Jar jar) throws Exception {
if(jarService.saveOrUpdate(jar)){
return Result.succeed("新增成功");
}else {
return Result.failed("新增失败");
}
}
/**
* 动态查询列表
*/
@PostMapping
public ProTableResult<Jar> listJars(@RequestBody JsonNode para) {
return jarService.selectForProTable(para);
}
/**
* 批量删除
*/
@DeleteMapping
public Result deleteMul(@RequestBody JsonNode para) {
if (para.size()>0){
List<Integer> error = new ArrayList<>();
for (final JsonNode item : para){
Integer id = item.asInt();
if(!jarService.removeById(id)){
error.add(id);
}
}
if(error.size()==0) {
return Result.succeed("删除成功");
}else {
return Result.succeed("删除部分成功,但"+error.toString()+"删除失败,共"+error.size()+"次失败。");
}
}else{
return Result.failed("请选择要删除的记录");
}
}
/**
* 获取指定ID的信息
*/
@PostMapping("/getOneById")
public Result getOneById(@RequestBody Jar jar) throws Exception {
jar = jarService.getById(jar.getId());
return Result.succeed(jar,"获取成功");
}
}
package com.dlink.mapper;
import com.dlink.db.mapper.SuperMapper;
import com.dlink.model.Jar;
import org.apache.ibatis.annotations.Mapper;
/**
* JarMapper
*
* @author wenmo
* @since 2021/11/13
**/
@Mapper
public interface JarMapper extends SuperMapper<Jar> {
}
package com.dlink.model;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.dlink.db.model.SuperEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Jar
*
* @author wenmo
* @since 2021/11/13
**/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("dlink_jar")
public class Jar extends SuperEntity {
private static final long serialVersionUID = 3769276772487490408L;
@TableField(fill = FieldFill.INSERT)
private String alias;
private String type;
private String path;
private String mainClass;
private String note;
}
package com.dlink.service;
import com.dlink.db.service.ISuperService;
import com.dlink.model.Jar;
/**
* JarService
*
* @author wenmo
* @since 2021/11/13
**/
public interface JarService extends ISuperService<Jar> {
}
package com.dlink.service.impl;
import com.dlink.db.service.impl.SuperServiceImpl;
import com.dlink.mapper.JarMapper;
import com.dlink.model.Jar;
import com.dlink.service.JarService;
import org.springframework.stereotype.Service;
/**
* JarServiceImpl
*
* @author wenmo
* @since 2021/11/13
**/
@Service
public class JarServiceImpl extends SuperServiceImpl<JarMapper, Jar> implements JarService {
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dlink.mapper.JarMapper">
<select id="selectForProTable" resultType="com.dlink.model.Jar">
select
a.*
from
dlink_jar a
<where>
1=1
<if test='param.name!=null and param.name!=""'>
and a.name like "%${param.name}%"
</if>
<if test='param.alias!=null and param.alias!=""'>
and a.alias like "%${param.alias}%"
</if>
<if test='param.path!=null and param.path!=""'>
and a.path like "%${param.path}%"
</if>
<if test='param.mainClass!=null and param.mainClass!=""'>
and a.main_class like "%${param.mainClass}%"
</if>
<if test='param.createTime!=null and param.createTime!=""'>
and a.create_time <![CDATA[>=]]> str_to_date( #{param.createTime},'%Y-%m-%d %H:%i:%s')
</if>
<if test='param.updateTime!=null and param.updateTime!=""'>
and a.update_time <![CDATA[>=]]> str_to_date( #{param.updateTime},'%Y-%m-%d %H:%i:%s')
</if>
<if test='ew.sqlSegment!=null and ew.sqlSegment!="" and !ew.sqlSegment.startsWith(" ORDER BY")'>
and
</if>
<if test='ew.sqlSegment!=null and ew.sqlSegment!=""'>
${ew.sqlSegment}
</if>
</where>
</select>
</mapper>
......@@ -380,7 +380,20 @@ CREATE TABLE `dlink_cluster_configuration` (
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `dlink_job` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称',
`alias` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '别名',
`type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '类型',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '文件路径',
`main_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '启动类',
`note` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '注释',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
......@@ -27,12 +27,25 @@ export default [
icon: 'consoleSql',
component: './FlinkSqlStudio',
},
{
path: '/taskcenter',
name: 'taskcenter',
icon: 'partition',
routes:[
/*{
path: '/task',
path: '/taskcenter/task',
name: 'task',
icon: 'partition',
icon: 'task',
component: './Task',
},*/
{
path: '/taskcenter/jar',
name: 'jar',
icon: 'code-sandbox',
component: './Jar',
}
]
},
{
path: '/clusters',
name: 'clusters',
......
......@@ -56,7 +56,9 @@ export default {
'menu.database': '数据源中心',
'menu.studio': 'FlinkSql IDE',
'menu.flinksqlstudio': 'FlinkSql Studio',
'menu.task': '作业中心',
'menu.taskcenter': '作业中心',
'menu.taskcenter.task': '作业管理',
'menu.taskcenter.jar': 'Jar 管理',
'menu.document': '文档中心',
'menu.dev': 'Dev 开发者工具',
'menu.dev.flink': 'Flink 计算框架',
......
......@@ -172,35 +172,13 @@ const ClusterConfigurationTableList: React.FC<{}> = (props: any) => {
dataIndex: 'createTime',
sorter: true,
valueType: 'dateTime',
hideInForm: true,
hideInTable: true,
renderFormItem: (item, {defaultRender, ...rest}, form) => {
const status = form.getFieldValue('status');
if (`${status}` === '0') {
return false;
}
if (`${status}` === '3') {
return <Input {...rest} placeholder="请输入异常原因!"/>;
}
return defaultRender(item);
},
},
{
title: '最近更新时间',
dataIndex: 'updateTime',
sorter: true,
valueType: 'dateTime',
hideInForm: true,
renderFormItem: (item, {defaultRender, ...rest}, form) => {
const status = form.getFieldValue('status');
if (`${status}` === '0') {
return false;
}
if (`${status}` === '3') {
return <Input {...rest} placeholder="请输入异常原因!"/>;
}
return defaultRender(item);
},
},
{
title: '操作',
......
import React, {useEffect, useState} from 'react';
import {Form, Button, Input, Modal, Select,Divider,Switch} from 'antd';
import {JarTableListItem} from "@/pages/Jar/data";
export type JarFormProps = {
onCancel: (flag?: boolean) => void;
onSubmit: (values: Partial<JarTableListItem>) => void;
modalVisible: boolean;
values: Partial<JarTableListItem>;
};
const Option = Select.Option;
const formLayout = {
labelCol: {span: 7},
wrapperCol: {span: 13},
};
const JarForm: React.FC<JarFormProps> = (props) => {
const [form] = Form.useForm();
const [formVals, setFormVals] = useState<Partial<JarTableListItem>>({
id: props.values.id,
name: props.values.name,
alias: props.values.alias,
type: props.values.type?props.values.type:'UserApp',
path: props.values.path,
mainClass: props.values.mainClass,
note: props.values.note,
enabled: props.values.enabled?props.values.enabled:true,
});
const {
onSubmit: handleSubmit,
onCancel: handleModalVisible,
modalVisible,
} = props;
const submitForm = async () => {
const fieldsValue = await form.validateFields();
setFormVals({...formVals, ...fieldsValue});
handleSubmit({...formVals, ...fieldsValue});
};
const renderContent = (formVals) => {
return (
<>
<Form.Item
name="type"
label="类型"
>
<Select defaultValue="UserApp" value="UserApp">
<Option value="UserApp">User App</Option>
</Select>
</Form.Item>
<Divider>Jar 配置</Divider>
<Form.Item
name="path"
label="文件路径"
help="指定 hdfs 上的文件路径"
rules={[{required: true, message: '请输入文件路径!'}]}
>
<Input placeholder="hdfs:///flink/app/demo.jar"/>
</Form.Item>
<Form.Item
name="mainClass"
label="启动类"
help="指定可执行 Jar 的启动类,(可选)"
>
<Input placeholder="com.dlink.app.MainApp"/>
</Form.Item>
<Divider orientation="left" plain>自定义配置(高优先级)</Divider>
<Divider>基本配置</Divider>
<Form.Item
name="name"
label="标识"
rules={[{required: true, message: '请输入名称!'}]}>
<Input placeholder="请输入唯一英文标识"/>
</Form.Item>
<Form.Item
name="alias"
label="名称"
>
<Input placeholder="请输入名称"/>
</Form.Item>
<Form.Item
name="note"
label="注释"
>
<Input.TextArea placeholder="请输入文本注释" allowClear
autoSize={{minRows: 3, maxRows: 10}}/>
</Form.Item>
<Form.Item
name="enabled"
label="是否启用">
<Switch checkedChildren="启用" unCheckedChildren="禁用"
defaultChecked={formVals.enabled}/>
</Form.Item>
</>
);
};
const renderFooter = () => {
return (
<>
<Button onClick={() => handleModalVisible(false)}>取消</Button>
<Button type="primary" onClick={() => submitForm()}>
完成
</Button>
</>
);
};
return (
<Modal
width={1200}
bodyStyle={{padding: '32px 40px 48px'}}
destroyOnClose
title={formVals.id?"维护Jar配置":"创建Jar配置"}
visible={modalVisible}
footer={renderFooter()}
onCancel={() => handleModalVisible()}
>
<Form
{...formLayout}
form={form}
initialValues={formVals}
>
{renderContent(formVals)}
</Form>
</Modal>
);
};
export default JarForm;
export type JarTableListItem = {
id: number,
name: string,
alias: string,
type: string,
path: string,
mainClass: string,
note: string,
enabled: boolean,
createTime: Date,
updateTime: Date,
};
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 ProDescriptions from '@ant-design/pro-descriptions';
import {JarTableListItem} from "@/pages/Jar/data";
import {handleAddOrUpdate, handleRemove, queryData, updateEnabled} from "@/components/Common/crud";
import JarForm from "@/pages/Jar/components/JarForm";
const url = '/api/jar';
const JarTableList: React.FC<{}> = (props: any) => {
const {dispatch} = props;
const [row, setRow] = useState<JarTableListItem>();
const [modalVisible, handleModalVisible] = useState<boolean>(false);
const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false);
const [formValues, setFormValues] = useState({});
const actionRef = useRef<ActionType>();
const [selectedRowsState, setSelectedRows] = useState<JarTableListItem[]>([]);
const editAndDelete = (key: string | number, currentItem: JarTableListItem) => {
if (key === 'edit') {
setFormValues(currentItem);
handleUpdateModalVisible(true);
} else if (key === 'delete') {
Modal.confirm({
title: '删除Jar配置',
content: '确定删除该Jar配置吗?',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await handleRemove(url, [currentItem]);
actionRef.current?.reloadAndRest?.();
}
});
}
};
const MoreBtn: React.FC<{
item: JarTableListItem;
}> = ({item}) => (
<Dropdown
overlay={
<Menu onClick={({key}) => editAndDelete(key, item)}>
<Menu.Item key="edit">编辑</Menu.Item>
<Menu.Item key="delete">删除</Menu.Item>
</Menu>
}
>
<a>
更多 <DownOutlined/>
</a>
</Dropdown>
);
const columns: ProColumns<JarTableListItem>[] = [
{
title: '名称',
dataIndex: 'name',
tip: '名称是唯一的',
sorter: true,
formItemProps: {
rules: [
{
required: true,
message: '名称为必填项',
},
],
},
render: (dom, entity) => {
return <a onClick={() => setRow(entity)}>{dom}</a>;
},
},
{
title: '集群配置ID',
dataIndex: 'id',
hideInTable: true,
hideInForm: true,
hideInSearch: true,
},
{
title: '别名',
sorter: true,
dataIndex: 'alias',
hideInTable: false,
},
{
title: '类型',
sorter: true,
dataIndex: 'type',
hideInForm: false,
hideInSearch: true,
hideInTable: false,
filters: [
{
text: 'UserApp',
value: 'UserApp',
}
],
filterMultiple: false,
valueEnum: {
'UserApp': {text: 'UserApp'},
},
},
{
title: '文件路径',
sorter: true,
dataIndex: 'path',
hideInTable: false,
},
{
title: '启动类',
sorter: true,
dataIndex: 'mainClass',
hideInTable: false,
},
{
title: '注释',
sorter: true,
valueType: 'textarea',
dataIndex: 'note',
hideInForm: false,
hideInSearch: true,
hideInTable: true,
},
{
title: '是否启用',
dataIndex: 'enabled',
hideInForm: true,
hideInSearch: true,
hideInTable: false,
filters: [
{
text: '已启用',
value: 1,
},
{
text: '已禁用',
value: 0,
},
],
filterMultiple: false,
valueEnum: {
true: {text: '已启用', status: 'Success'},
false: {text: '已禁用', status: 'Error'},
},
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
valueType: 'dateTime',
hideInTable: true
},
{
title: '最近更新时间',
dataIndex: 'updateTime',
sorter: true,
valueType: 'dateTime',
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
onClick={() => {
handleUpdateModalVisible(true);
setFormValues(record);
}}
>
配置
</a>,
<MoreBtn key="more" item={record}/>,
],
},
];
return (
<PageContainer>
<ProTable<JarTableListItem>
headerTitle="Jar 配置管理"
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
toolBarRender={() => [
<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>
被禁用的集群配置共 {selectedRowsState.length - selectedRowsState.reduce((pre, item) => pre + (item.enabled ? 1 : 0), 0)}
</span>
</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>
<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) {
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 () => ({
data: row || {},
})}
params={{
id: row?.name,
}}
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