Commit 9bd1618c authored by godkaikai's avatar godkaikai

预览数据

parent 9cb959f9
......@@ -53,7 +53,6 @@ public class ClusterController {
@DeleteMapping
public Result deleteMul(@RequestBody JsonNode para) {
if (para.size()>0){
boolean isAdmin = false;
List<Integer> error = new ArrayList<>();
for (final JsonNode item : para){
Integer id = item.asInt();
......@@ -61,7 +60,7 @@ public class ClusterController {
error.add(id);
}
}
if(error.size()==0&&!isAdmin) {
if(error.size()==0) {
return Result.succeed("删除成功");
}else {
return Result.succeed("删除部分成功,但"+error.toString()+"删除失败,共"+error.size()+"次失败。");
......
package com.dlink.controller;
import com.dlink.common.result.ProTableResult;
import com.dlink.common.result.Result;
import com.dlink.model.Document;
import com.dlink.service.DocumentService;
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.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;
/**
* DocumentController
*
* @author wenmo
* @since 2021/6/3
**/
@Slf4j
@RestController
@RequestMapping("/api/document")
public class DocumentController {
@Autowired
private DocumentService documentService;
/**
* 新增或者更新
*/
@PutMapping
public Result saveOrUpdate(@RequestBody Document document) throws Exception {
if(documentService.saveOrUpdate(document)){
return Result.succeed("新增成功");
}else {
return Result.failed("新增失败");
}
}
/**
* 动态查询列表
*/
@PostMapping
public ProTableResult<Document> listDocuments(@RequestBody JsonNode para) {
return documentService.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(!documentService.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 Document document) throws Exception {
document = documentService.getById(document.getId());
return Result.succeed(document,"获取成功");
}
}
package com.dlink.controller;
import com.dlink.common.result.Result;
import com.dlink.dto.StudioDDLDTO;
import com.dlink.dto.StudioExecuteDTO;
import com.dlink.model.Task;
import com.dlink.result.RunResult;
......@@ -34,4 +35,13 @@ public class StudioController {
RunResult runResult = studioService.executeSql(studioExecuteDTO);
return Result.succeed(runResult,"执行成功");
}
/**
* 进行DDL操作
*/
@PostMapping("/executeDDL")
public Result executeDDL(@RequestBody StudioDDLDTO studioDDLDTO) throws Exception {
RunResult runResult = studioService.executeDDL(studioDDLDTO);
return Result.succeed(runResult,"执行成功");
}
}
package com.dlink.dto;
import lombok.Getter;
import lombok.Setter;
/**
* StudioDDLDTO
*
* @author wenmo
* @since 2021/6/3
*/
@Getter
@Setter
public class StudioDDLDTO {
private String session;
private String statement;
private Integer clusterId=0;
}
package com.dlink.mapper;
import com.dlink.db.mapper.SuperMapper;
import com.dlink.model.Document;
import org.apache.ibatis.annotations.Mapper;
/**
* DocumentMapper
*
* @author wenmo
* @since 2021/6/3 14:31
**/
@Mapper
public interface DocumentMapper extends SuperMapper<Document> {
}
package com.dlink.model;
import com.baomidou.mybatisplus.annotation.TableName;
import com.dlink.db.model.SuperEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Document
*
* @author wenmo
* @since 2021/6/3 14:27
**/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("dlink_flink_document")
public class Document extends SuperEntity {
private static final long serialVersionUID = -6340080980759236641L;
private String category;
private String type;
private String subtype;
private String description;
private String version;
private String likeNum;
}
package com.dlink.service;
import com.dlink.db.service.ISuperService;
import com.dlink.model.Document;
/**
* DocumentService
*
* @author wenmo
* @since 2021/6/3 14:35
**/
public interface DocumentService extends ISuperService<Document> {
}
package com.dlink.service;
import com.dlink.dto.StudioDDLDTO;
import com.dlink.dto.StudioExecuteDTO;
import com.dlink.result.RunResult;
......@@ -11,4 +12,6 @@ import com.dlink.result.RunResult;
*/
public interface StudioService {
RunResult executeSql(StudioExecuteDTO studioExecuteDTO);
RunResult executeDDL(StudioDDLDTO studioDDLDTO);
}
package com.dlink.service.impl;
import com.dlink.db.service.impl.SuperServiceImpl;
import com.dlink.mapper.DocumentMapper;
import com.dlink.model.Document;
import com.dlink.service.DocumentService;
import org.springframework.stereotype.Service;
/**
* DocumentServiceImpl
*
* @author wenmo
* @since 2021/6/3 14:36
**/
@Service
public class DocumentServiceImpl extends SuperServiceImpl<DocumentMapper, Document> implements DocumentService {
}
......@@ -2,6 +2,7 @@ package com.dlink.service.impl;
import com.dlink.assertion.Assert;
import com.dlink.cluster.FlinkCluster;
import com.dlink.dto.StudioDDLDTO;
import com.dlink.dto.StudioExecuteDTO;
import com.dlink.executor.Executor;
import com.dlink.executor.ExecutorSetting;
......@@ -27,13 +28,38 @@ public class StudioServiceImpl implements StudioService {
@Override
public RunResult executeSql(StudioExecuteDTO studioExecuteDTO) {
studioExecuteDTO.setSession(studioExecuteDTO.getClusterId()+"_"+studioExecuteDTO.getSession());
String ExecuteType = Executor.REMOTE;
String host =null;
Cluster cluster = clusterService.getById(studioExecuteDTO.getClusterId());
Assert.check(cluster);
String host = FlinkCluster.testFlinkJobManagerIP(cluster.getHosts(), cluster.getJobManagerHost());
Assert.checkHost(host);
if(studioExecuteDTO.getClusterId()==0&&cluster==null){
ExecuteType = Executor.LOCAL;
}else {
Assert.check(cluster);
host = FlinkCluster.testFlinkJobManagerIP(cluster.getHosts(), cluster.getJobManagerHost());
Assert.checkHost(host);
}
JobManager jobManager = new JobManager(host,studioExecuteDTO.getSession(),studioExecuteDTO.getMaxRowNum());
return jobManager.execute(studioExecuteDTO.getStatement(), new ExecutorSetting(
Executor.REMOTE,studioExecuteDTO.getCheckPoint(),studioExecuteDTO.getParallelism(),
ExecuteType,studioExecuteDTO.getCheckPoint(),studioExecuteDTO.getParallelism(),
studioExecuteDTO.isFragment(),studioExecuteDTO.getSavePointPath()));
}
@Override
public RunResult executeDDL(StudioDDLDTO studioDDLDTO) {
studioDDLDTO.setSession(studioDDLDTO.getClusterId()+"_"+studioDDLDTO.getSession());
String ExecuteType = Executor.REMOTE;
String host =null;
Cluster cluster = clusterService.getById(studioDDLDTO.getClusterId());
if(studioDDLDTO.getClusterId()==0&&cluster==null){
ExecuteType = Executor.LOCAL;
}else {
Assert.check(cluster);
host = FlinkCluster.testFlinkJobManagerIP(cluster.getHosts(), cluster.getJobManagerHost());
Assert.checkHost(host);
}
JobManager jobManager = new JobManager(host,studioDDLDTO.getSession(),1000);
return jobManager.execute(studioDDLDTO.getStatement(), new ExecutorSetting(
ExecuteType));
}
}
<?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.DocumentMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.dlink.model.Document">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="category" property="category" />
<result column="type" property="type" />
<result column="subtype" property="subtype" />
<result column="description" property="description" />
<result column="version" property="version" />
<result column="like_num" property="likeNum" />
<result column="enabled" property="enabled" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, category, type,subtype,description, version,like_num, enabled, create_time, update_time
</sql>
<select id="selectForProTable" resultType="com.dlink.model.Document">
select
a.*
from
dlink_flink_document a
<where>
1=1
<if test='param.name!=null and param.name!=""'>
and a.name like "%${param.name}%"
</if>
<if test='param.description!=null and param.description!=""'>
and a.description like "%${param.description}%"
</if>
<if test='param.type!=null and param.type!=""'>
and a.type = #{param.type}
</if>
<if test='param.subtype!=null and param.subtype!=""'>
and a.subtype = #{param.subtype}
</if>
<if test='param.version!=null and param.version!=""'>
and a.version = #{param.version}
</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>
......@@ -36,6 +36,10 @@ public class ExecutorSetting {
this.savePointPath = savePointPath;
}
public boolean isRemote(){
return type.equals(Executor.REMOTE);
}
public String getType() {
return type;
}
......
......@@ -26,6 +26,12 @@ public class LocalStreamExecutor extends Executor {
public LocalStreamExecutor(ExecutorSetting executorSetting) {
this.executorSetting = executorSetting;
this.environment = StreamExecutionEnvironment.createLocalEnvironment();
if(executorSetting.getCheckpoint()!=null&&executorSetting.getCheckpoint()>0){
environment.enableCheckpointing(executorSetting.getCheckpoint());
}
if(executorSetting.getParallelism()!=null&&executorSetting.getParallelism()>0){
environment.setParallelism(executorSetting.getParallelism());
}
stEnvironment = CustomTableEnvironmentImpl.create(environment);
if(executorSetting.isUseSqlFragment()){
stEnvironment.useSqlFragment();
......
......@@ -30,6 +30,12 @@ public class RemoteStreamExecutor extends Executor {
this.executorSetting = executorSetting;
synchronized (RemoteStreamExecutor.class){
this.environment = StreamExecutionEnvironment.createRemoteEnvironment(environmentSetting.getHost(), environmentSetting.getPort());
if(executorSetting.getCheckpoint()!=null&&executorSetting.getCheckpoint()>0){
environment.enableCheckpointing(executorSetting.getCheckpoint());
}
if(executorSetting.getParallelism()!=null&&executorSetting.getParallelism()>0){
environment.setParallelism(executorSetting.getParallelism());
}
if(stEnvironment == null){
stEnvironment = CustomTableEnvironmentImpl.create(environment);
}
......
......@@ -78,7 +78,11 @@ public class JobManager {
if (executorEntity != null) {
executor = executorEntity.getExecutor();
} else {
executor = Executor.build(new EnvironmentSetting(flinkHost, FlinkConstant.PORT), executorSetting);
if(executorSetting.isRemote()) {
executor = Executor.build(new EnvironmentSetting(flinkHost, FlinkConstant.PORT), executorSetting);
}else{
executor = Executor.build(null, executorSetting);
}
SessionPool.push(new ExecutorEntity(sessionId, executor));
}
String[] Statements = statement.split(";");
......@@ -99,18 +103,23 @@ public class JobManager {
runResult.setResult(result);
runResult.setTime(timeElapsed);
runResult.setFinishDate(LocalDateTime.now());
runResult.setSuccess(true);
if(tableResult.getJobClient().isPresent()) {
runResult.setJobId(tableResult.getJobClient().get().getJobID().toString());
runResult.setSuccess(tableResult.getJobClient().get().getJobStatus().isDone());
}else{
runResult.setSuccess(true);
}
}
} catch (Exception e) {
e.printStackTrace();
StackTraceElement[] trace = e.getStackTrace();
StringBuffer resMsg = new StringBuffer("");
for (StackTraceElement s : trace) {
resMsg.append(" </br> " + s + " ");
resMsg.append(" \n " + s + " ");
}
runResult.setFinishDate(LocalDateTime.now());
runResult.setSuccess(false);
runResult.setError(LocalDateTime.now().toString() + ":" + "运行第" + currentIndex + "行sql时出现异常:" + e.getMessage() + "</br> >>>堆栈信息<<<" + resMsg.toString());
runResult.setError(LocalDateTime.now().toString() + ":" + "运行第" + currentIndex + "行sql时出现异常:" + e.getMessage() + " \n >>>堆栈信息<<<" + resMsg.toString());
return runResult;
}
return runResult;
......
......@@ -12,6 +12,7 @@ import java.time.LocalDateTime;
**/
public class RunResult {
private String sessionId;
private String jobId;
private String statement;
private String flinkHost;
private Integer flinkPort;
......@@ -34,6 +35,14 @@ public class RunResult {
this.setting = setting;
}
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public ExecutorSetting getSetting() {
return setting;
}
......
This diff is collapsed.
......@@ -45,7 +45,7 @@ export default [
icon: 'cluster',
component: './Cluster',
},
{
/*{
path: '/dev',
name: 'dev',
icon: 'crown',
......@@ -78,7 +78,7 @@ export default [
],
},
],
},
},*/
/*{
path: '/admin',
name: 'admin',
......@@ -94,7 +94,7 @@ export default [
},
],
},*/
{
/*{
path: '/demo',
name: 'demo',
icon: 'crown',
......@@ -139,7 +139,7 @@ export default [
],
},
],
},
},*/
{
path: '/',
redirect: '/welcome',
......
import {message, Input, Button, Space, Table, Dropdown, Menu, Empty,Divider} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
import {useState} from "react";
// import Highlighter from 'react-highlight-words';
import { SearchOutlined,DownOutlined,TableOutlined } from '@ant-design/icons';
import React from "react";
import {executeDDL} from "@/pages/FlinkSqlStudio/service";
const StudioConnector = (props:any) => {
const {current} = props;
const [tableData,setTableData] = useState<[]>([]);
const [loadings,setLoadings] = useState<boolean[]>([]);
const [searchText,setSearchText] = useState<string>('');
const [searchedColumn,setSearchedColumn] = useState<string>('');
const getColumnSearchProps = (dIndex) => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
placeholder={`Search ${dIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dIndex)}
style={{ marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
搜索
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
重置
</Button>
<Button
type="link"
size="small"
onClick={() => {
setSearchText(selectedKeys[0]);
setSearchedColumn(dIndex);
}}
>
过滤
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dIndex]
? record[dIndex].toString().toLowerCase().includes(value.toLowerCase())
: '',
/*render: text =>
searchedColumn === dIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text ? text.toString() : ''}
/>
) : (
text
),*/
});
const handleSearch = (selectedKeys, confirm, dIndex) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dIndex);
};
const handleReset = (clearFilters) => {
clearFilters();
setSearchText('');
};
const MoreBtn: React.FC<{
item:any
}> = ({item}) => (
<Dropdown
overlay={
<Menu onClick={({key}) => keyEvent(key, item)}>
<Menu.Item key="desc">描述</Menu.Item>
<Menu.Item key="delete">删除</Menu.Item>
</Menu>
}
>
<a>
更多 <DownOutlined/>
</a>
</Dropdown>
);
const keyEvent=(key, item)=>{
if(key=='delete'){
let newLoadings = [...loadings];
newLoadings[1] = true;
setLoadings(newLoadings);
const res = executeDDL({
statement:"drop table "+item.tablename,
clusterId: current.task.clusterId,
session:current.task.session,
});
res.then((result)=>{
if(result.datas.success){
let newTableData = tableData;
for (let i=0; i<newTableData.length; i++) {
if (newTableData[i].tablename == item.tablename) {
newTableData.splice(i, 1);
setTableData(newTableData);
break;
}
}
}
let newLoadings = [...loadings];
newLoadings[1] = false;
setLoadings(newLoadings);
});
}else{
message.warn("敬请期待");
}
};
const getTables = () => {
let newLoadings = [...loadings];
newLoadings[0] = true;
setLoadings(newLoadings);
const res = executeDDL({
statement:"show tables",
clusterId: current.task.clusterId,
session:current.task.session,
});
res.then((result)=>{
if(result.datas.result.rowData.length>0){
setTableData(result.datas.result.rowData);
}else {
setTableData([]);
}
let newLoadings = [...loadings];
newLoadings[0] = false;
setLoadings(newLoadings);
});
};
const getColumns=()=>{
let columns:any=[{
title: "表名",
dataIndex: "tablename",
key: "tablename",
sorter: true,
...getColumnSearchProps("tablename"),
},{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
onClick={() => {
message.warn('敬请期待');
}}
>
描述
</a>,<Divider type="vertical" />,<a
onClick={() => {
keyEvent('delete',record);
}}
>
删除
</a>
],
},];
return columns;
};
return (
<>
<Button
type="primary"
icon={<TableOutlined />}
loading={loadings[0]}
onClick={() => getTables()}
>
获取Connectors
</Button>
{tableData.length>0?(<Table dataSource={tableData} columns={getColumns()} />):(<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />)}
</>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
}))(StudioConnector);
import {DownOutlined, HeartOutlined, PlusOutlined, UserOutlined} from '@ant-design/icons';
import {Button, message, Input, Drawer, Modal} from 'antd';
import React, {useState, useRef} from 'react';
import {PageContainer, FooterToolbar} from '@ant-design/pro-layout';
import type {ProColumns, ActionType} from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import ProDescriptions from '@ant-design/pro-descriptions';
import type {DocumentTableListItem} from '@/pages/Document/data.d';
import { queryData,} from "@/components/Common/crud";
import {connect} from "umi";
import {StateType} from "@/pages/FlinkSqlStudio/model";
const url = '/api/document';
const StudioDocument = () => {
const actionRef = useRef<ActionType>();
const [row, setRow] = useState<DocumentTableListItem>();
const columns: ProColumns<DocumentTableListItem>[] = [
{
title: '名称',
dataIndex: 'name',
tip: '名称是唯一的',
sorter: true,
width:'400px',
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: 'category',
hideInForm: false,
hideInSearch: true,
hideInTable: true,
filters: [
{
text: '函数',
value: 'function',
}
],
filterMultiple: false,
valueEnum: {
'function': { text: '函数'},
},
},
{
title: '类型',
sorter: true,
dataIndex: 'type',
hideInForm: false,
hideInSearch: true,
hideInTable: false,
filters: [
{
text: '内置函数',
value: '内置函数',
},
{
text: 'UDF',
value: 'UDF',
},
],
filterMultiple: false,
valueEnum: {
'内置函数': { text: '内置函数'},
'UDF': { text: 'UDF'},
},
},
{
title: '子类型',
sorter: true,
dataIndex: 'subtype',
hideInForm: false,
hideInSearch: true,
hideInTable: false,
filters: [
{
text: '比较函数',
value: '比较函数',
},
{
text: '逻辑函数',
value: '逻辑函数',
},{
text: '算术函数',
value: '算术函数',
},{
text: '字符串函数',
value: '字符串函数',
},{
text: '时间函数',
value: '时间函数',
},{
text: '条件函数',
value: '条件函数',
},{
text: '类型转换函数',
value: '类型转换函数',
},{
text: 'Collection 函数',
value: 'Collection 函数',
},{
text: 'Value Collection 函数',
value: 'Value Collection 函数',
},{
text: 'Value Access 函数',
value: 'Value Access 函数',
},{
text: '分组函数',
value: '分组函数',
},{
text: 'hash函数',
value: 'hash函数',
},{
text: '聚合函数',
value: '聚合函数',
},{
text: '列函数',
value: '列函数',
},{
text: '表值聚合函数',
value: '表值聚合函数',
},{
text: '其他函数',
value: '其他函数',
},
],
filterMultiple: false,
valueEnum: {
'比较函数': { text: '比较函数'},
'逻辑函数': { text: '逻辑函数'},
'算术函数': { text: '算术函数'},
'字符串函数': { text: '字符串函数'},
'时间函数': { text: '时间函数'},
'条件函数': { text: '条件函数'},
'类型转换函数': { text: '类型转换函数'},
'Collection 函数': { text: 'Collection 函数'},
'Value Collection 函数': { text: 'Value Collection 函数'},
'Value Access 函数': { text: 'Value Access 函数'},
'分组函数': { text: '分组函数'},
'hash函数': { text: 'hash函数'},
'聚合函数': { text: '聚合函数'},
'列函数': { text: '列函数'},
'表值聚合函数': { text: '表值聚合函数'},
'其他函数': { text: '其他函数'},
},
},
{
title: '描述',
sorter: true,
dataIndex: 'description',
valueType: 'textarea',
hideInForm: false,
hideInSearch: false,
hideInTable: false,
width:'400px',
},
{
title: '版本',
sorter: true,
dataIndex: 'version',
hideInForm: true,
hideInSearch: true,
hideInTable: true,
},{
title: '赞',
sorter: true,
dataIndex: 'likeNum',
hideInForm: true,
hideInSearch: true,
hideInTable: false,
},
{
title: '是否启用',
dataIndex: 'enabled',
hideInForm: true,
hideInSearch: true,
hideInTable: true,
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',
hideInForm: true,
hideInTable:true,
hideInSearch: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,
hideInTable:true,
hideInSearch: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);
},
},
];
return (
<>
<ProTable<DocumentTableListItem>
headerTitle="文档管理"
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
request={(params, sorter, filter) => queryData(url,{...params, sorter, filter})}
columns={columns}
/>
<Drawer
width={600}
visible={!!row}
onClose={() => {
setRow(undefined);
}}
closable={false}
>
{row?.name && (
<ProDescriptions<DocumentTableListItem>
column={2}
title={row?.name}
request={async () => ({
data: row || {},
})}
params={{
id: row?.name,
}}
columns={columns}
/>
)}
</Drawer>
</>);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
}))(StudioDocument);
import {Typography, Divider, Badge, Empty} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
const { Title, Paragraph, Text, Link } = Typography;
const StudioHistory = (props:any) => {
const {current} = props;
return (
<Typography>
{current.console.result.map((item)=> {
return (<Paragraph>
<blockquote><Link href={`http://${item.flinkHost}:${item.flinkPort}`} target="_blank">
[{item.sessionId}:{item.flinkHost}:{item.flinkPort}]
</Link> <Divider type="vertical" />{item.finishDate}
<Divider type="vertical" />
{!item.success ? <><Badge status="error"/><Text type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical" />
{item.jobId&&<Text code>{item.jobId}</Text>}
<Text keyboard>{item.time}ms</Text></blockquote>
{item.statement && (<pre style={{height:'40px'}}>{item.statement}</pre>)}
{item.msg ? item.msg : ''}
{item.error && (<pre style={{height:'100px'}}>{item.error}</pre>)}
</Paragraph>)
})}
{current.console.result.length==0?<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />:''}
</Typography>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
}))(StudioHistory);
import { Typography,Divider,Badge } from "antd";
import {Typography, Divider, Badge, Empty} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
......@@ -10,21 +10,27 @@ const StudioMsg = (props:any) => {
return (
<Typography>
{current.console.result.map((item)=> {
return (<Paragraph>
<blockquote><Link href={`http://${item.flinkHost}:${item.flinkPort}`} target="_blank">
[{item.sessionId}:{item.flinkHost}:{item.flinkPort}]
</Link> <Divider type="vertical" />{item.finishDate}
<Divider type="vertical" />
{!item.success ? <><Badge status="error"/><Text type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical" />
<Text keyboard>{item.time}ms</Text></blockquote>
{item.statement && (<pre style={{height:'40px'}}>{item.statement}</pre>)}
{item.msg ? item.msg : ''}
{item.error && (<pre style={{height:'100px'}}>{item.error}</pre>)}
</Paragraph>)
{current.console.result.map((item,index)=> {
if(index==0) {
return (<Paragraph>
<blockquote><Link href={`http://${item.flinkHost}:${item.flinkPort}`} target="_blank">
[{item.sessionId}:{item.flinkHost}:{item.flinkPort}]
</Link> <Divider type="vertical"/>{item.finishDate}
<Divider type="vertical"/>
{!item.success ? <><Badge status="error"/><Text type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical"/>
{item.jobId&&<Text code>{item.jobId}</Text>}
<Text keyboard>{item.time}ms</Text></blockquote>
{item.statement && (<pre style={{height: '100px'}}>{item.statement}</pre>)}
{item.msg ? item.msg : ''}
{item.error && (<pre style={{height: '100px'}}>{item.error}</pre>)}
</Paragraph>)
}else{
return '';
}
})}
{current.console.result.length==0?<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />:''}
</Typography>
);
};
......
import { Typography,Divider,Badge,Select,Tag,Form} from "antd";
import {Typography, Input, Button, Space, Table, Select, Tag, Form, Empty} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
import {useState} from "react";
// import Highlighter from 'react-highlight-words';
import { SearchOutlined } from '@ant-design/icons';
const { Option } = Select;
const { Title, Paragraph, Text, Link } = Typography;
......@@ -9,16 +12,101 @@ const { Title, Paragraph, Text, Link } = Typography;
const StudioTable = (props:any) => {
const {current} = props;
const [dataIndex,setDataIndex] = useState<number>(0);
const [searchText,setSearchText] = useState<string>('');
const [searchedColumn,setSearchedColumn] = useState<string>('');
const getColumnSearchProps = (dIndex) => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
placeholder={`Search ${dIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dIndex)}
style={{ marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
搜索
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
重置
</Button>
<Button
type="link"
size="small"
onClick={() => {
setSearchText(selectedKeys[0]);
setSearchedColumn(dIndex);
}}
>
过滤
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dIndex]
? record[dIndex].toString().toLowerCase().includes(value.toLowerCase())
: '',
/*render: text =>
searchedColumn === dIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text ? text.toString() : ''}
/>
) : (
text
),*/
});
const handleSearch = (selectedKeys, confirm, dIndex) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dIndex);
};
const handleReset = (clearFilters) => {
clearFilters();
setSearchText('');
};
const getColumns=(columns:[])=>{
let datas:any=[];
columns.map((item)=> {
datas.push({
title: item,
dataIndex: item,
key: item,
sorter: true,
...getColumnSearchProps(item),
});
});
return datas;
};
const onChange=(val:number)=>{
setDataIndex(val);
};
return (
<Typography>
<Form.Item label="当前执行记录" tooltip="选择最近的执行记录,仅包含成功的记录">
<Select
//mode="multiple"
style={{ width: '100%' }}
placeholder="选择最近的执行记录"
defaultValue={[0]}
optionLabelProp="label"
onChange={onChange}
>
{current.console.result.map((item,index)=> {
if(item.success) {
......@@ -31,8 +119,9 @@ const StudioTable = (props:any) => {
</Option>)
}
})}
</Select>
</Select>
</Form.Item>
{current.console.result[dataIndex]&&current.console.result[dataIndex].result?(<Table dataSource={current.console.result[dataIndex].result.rowData} columns={getColumns(current.console.result[dataIndex].result.columns)} />):(<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />)}
</Typography>
);
};
......
......@@ -6,6 +6,8 @@ import {connect} from "umi";
import styles from "./index.less";
import StudioMsg from "./StudioMsg";
import StudioTable from "./StudioTable";
import StudioHistory from "./StudioHistory";
import StudioDocument from "./StudioDocument";
const { TabPane } = Tabs;
......@@ -68,7 +70,7 @@ const StudioConsole = (props:any) => {
}
key="5"
>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
<StudioHistory />
</TabPane>
<TabPane
tab={
......@@ -79,7 +81,7 @@ const StudioConsole = (props:any) => {
}
key="6"
>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
<StudioDocument />
</TabPane>
<TabPane
tab={
......
......@@ -8,7 +8,42 @@ const Completion =[
insertText: 'SUM(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的求和'
}
},
{
label: 'SQRT(number)',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'SQRT(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的平方根'
},
{
label: 'SIN(number)',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'SIN(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的正弦值'
},
{
label: 'SINH(number)',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'SINH(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的双曲正弦值'
},
{
label: 'SIGN(number)',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'SIGN(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的符合'
},
{
label: 'SUBSTRING(string,integer1,integer2)',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'SUBSTRING(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定字符串的子字符串'
},
];
export default Completion;
......@@ -31,4 +31,11 @@ export type CompletionItem ={
}
export type StudioParam = {
statement:string,
checkPoint?: number,
savePointPath?: string,
parallelism?: number,
fragment?: boolean,
clusterId: number,
session:string,
maxRowNum?: number,
}
......@@ -29,10 +29,9 @@ const FlinkSqlEditor = (props:any) => {
selectOnLineNumbers: true,
renderSideBySide: false,
},
sql=props.current.value,
current=props.current,
// sql,
dispatch,
monaco,
} = props
;
......@@ -48,6 +47,7 @@ const FlinkSqlEditor = (props:any) => {
const cache: any = useRef(code.current);
const [refresh, setRefresh] = React.useState<boolean>(false);
const [selectValue, setSelectValue] = React.useState<string>("");
useEffect(
() => () => {
......@@ -113,7 +113,6 @@ const FlinkSqlEditor = (props:any) => {
}
});
code.current = newSecondRightFields; // 数组长度永远为1
// 提示项设值
provider = monaco.languages.registerCompletionItemProvider('sql', {
provideCompletionItems() {
......@@ -149,6 +148,7 @@ const FlinkSqlEditor = (props:any) => {
return (
<React.Fragment>
<MonacoEditor
ref={monaco}
width={width}
height={height}
language={language}
......@@ -158,6 +158,7 @@ return (
theme="vs-dark"
editorDidMount={editorDidMountHandle}
/>
{selectValue}
</React.Fragment>
);
};
......@@ -166,4 +167,5 @@ export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
sql: Studio.sql,
tabs: Studio.tabs,
monaco: Studio.monaco,
}))(FlinkSqlEditor);
import {message, Input, Button, Space, Table, Dropdown, Menu, Empty,Divider} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
import {useState} from "react";
// import Highlighter from 'react-highlight-words';
import { SearchOutlined,DownOutlined,TableOutlined } from '@ant-design/icons';
import React from "react";
import {executeDDL} from "@/pages/FlinkSqlStudio/service";
const StudioConnector = (props:any) => {
const {current} = props;
const [tableData,setTableData] = useState<[]>([]);
const [loadings,setLoadings] = useState<boolean[]>([]);
const [searchText,setSearchText] = useState<string>('');
const [searchedColumn,setSearchedColumn] = useState<string>('');
const getColumnSearchProps = (dIndex) => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
placeholder={`Search ${dIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dIndex)}
style={{ marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
搜索
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
重置
</Button>
<Button
type="link"
size="small"
onClick={() => {
setSearchText(selectedKeys[0]);
setSearchedColumn(dIndex);
}}
>
过滤
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dIndex]
? record[dIndex].toString().toLowerCase().includes(value.toLowerCase())
: '',
/*render: text =>
searchedColumn === dIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text ? text.toString() : ''}
/>
) : (
text
),*/
});
const handleSearch = (selectedKeys, confirm, dIndex) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dIndex);
};
const handleReset = (clearFilters) => {
clearFilters();
setSearchText('');
};
const MoreBtn: React.FC<{
item:any
}> = ({item}) => (
<Dropdown
overlay={
<Menu onClick={({key}) => keyEvent(key, item)}>
<Menu.Item key="desc">描述</Menu.Item>
<Menu.Item key="delete">删除</Menu.Item>
</Menu>
}
>
<a>
更多 <DownOutlined/>
</a>
</Dropdown>
);
const keyEvent=(key, item)=>{
if(key=='delete'){
let newLoadings = [...loadings];
newLoadings[1] = true;
setLoadings(newLoadings);
const res = executeDDL({
statement:"drop table "+item.tablename,
clusterId: current.task.clusterId,
session:current.task.session,
});
res.then((result)=>{
if(result.datas.success){
let newTableData = tableData;
for (let i=0; i<newTableData.length; i++) {
if (newTableData[i].tablename == item.tablename) {
newTableData.splice(i, 1);
setTableData(newTableData);
break;
}
}
}
let newLoadings = [...loadings];
newLoadings[1] = false;
setLoadings(newLoadings);
});
}else{
message.warn("敬请期待");
}
};
const getTables = () => {
let newLoadings = [...loadings];
newLoadings[0] = true;
setLoadings(newLoadings);
const res = executeDDL({
statement:"show tables",
clusterId: current.task.clusterId,
session:current.task.session,
});
res.then((result)=>{
if(result.datas.result.rowData.length>0){
setTableData(result.datas.result.rowData);
}else {
setTableData([]);
}
let newLoadings = [...loadings];
newLoadings[0] = false;
setLoadings(newLoadings);
});
};
const getColumns=()=>{
let columns:any=[{
title: "表名",
dataIndex: "tablename",
key: "tablename",
sorter: true,
...getColumnSearchProps("tablename"),
},{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
onClick={() => {
message.warn('敬请期待');
}}
>
描述
</a>,<Divider type="vertical" />,<a
onClick={() => {
keyEvent('delete',record);
}}
>
删除
</a>
],
},];
return columns;
};
return (
<>
<Button
type="primary"
icon={<TableOutlined />}
loading={loadings[0]}
onClick={() => getTables()}
>
获取Connectors
</Button>
{tableData.length>0?(<Table dataSource={tableData} columns={getColumns()} />):(<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />)}
</>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
}))(StudioConnector);
import styles from "./index.less";
import {Menu, Dropdown, Tooltip, Row, Col,Popconfirm,Badge} from "antd";
import {Menu, Dropdown, Tooltip, Row, Col,Popconfirm,notification} from "antd";
import {PauseCircleTwoTone, CopyTwoTone, DeleteTwoTone,PlayCircleTwoTone,DiffTwoTone,
FileAddTwoTone,FolderOpenTwoTone,SafetyCertificateTwoTone,SaveTwoTone,FlagTwoTone,EnvironmentOutlined} from "@ant-design/icons";
FileAddTwoTone,FolderOpenTwoTone,SafetyCertificateTwoTone,SaveTwoTone,FlagTwoTone,
EnvironmentOutlined,SmileOutlined} from "@ant-design/icons";
import Space from "antd/es/space";
import Divider from "antd/es/divider";
import Button from "antd/es/button/button";
import Breadcrumb from "antd/es/breadcrumb/Breadcrumb";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
import {useEffect, useState} from "react";
import {handleAddOrUpdate, postAll} from "@/components/Common/crud";
import { postAll} from "@/components/Common/crud";
import {executeSql} from "@/pages/FlinkSqlStudio/service";
const {SubMenu} = Menu;
//<Button shape="circle" icon={<CaretRightOutlined />} />
const menu = (
<Menu>
<Menu.Item>1st menu item</Menu.Item>
<Menu.Item>2nd menu item</Menu.Item>
<SubMenu title="sub menu">
<Menu.Item>3rd menu item</Menu.Item>
<Menu.Item>4th menu item</Menu.Item>
</SubMenu>
<SubMenu title="disabled sub menu" disabled>
<Menu.Item>5d menu item</Menu.Item>
<Menu.Item>6th menu item</Menu.Item>
</SubMenu>
<Menu.Item>敬请期待</Menu.Item>
</Menu>
);
const StudioMenu = (props: any) => {
const {tabs,current,currentPath,form,dispatch} = props;
const [pathItem, setPathItem] = useState<[]>();
const {tabs,current,currentPath,form,dispatch,monaco} = props;
const executeSql = () => {
const execute = () => {
let selection = monaco.current.editor.getSelection();
let selectsql = monaco.current.editor.getModel().getValueInRange(selection);
if(selectsql==null||selectsql==''){
selectsql=current.value;
}
let param ={
session:current.task.session,
statement:current.value,
statement:selectsql,
clusterId:current.task.clusterId,
checkPoint:current.task.checkPoint,
parallelism:current.task.parallelism,
......@@ -46,8 +40,17 @@ const StudioMenu = (props: any) => {
savePointPath:current.task.savePointPath,
};
const key = current.key;
const result = postAll('api/studio/executeSql',param);
const taskKey = (Math.random()*1000)+'';
notification.success({
message: `${param.clusterId+"_"+param.session} 新任务正在执行`,
description: param.statement,
duration:null,
key:taskKey,
icon: <SmileOutlined style={{ color: '#108ee9' }} />,
});
const result = executeSql(param);
result.then(res=>{
notification.close(taskKey);
let newTabs = tabs;
for(let i=0;i<newTabs.panes.length;i++){
if(newTabs.panes[i].key==key){
......@@ -59,7 +62,6 @@ const StudioMenu = (props: any) => {
break;
}
}
console.log(newTabs);
dispatch&&dispatch({
type: "Studio/saveTabs",
payload: newTabs,
......@@ -67,14 +69,6 @@ const StudioMenu = (props: any) => {
})
};
const buildMsg=(res)=>{
const result = res.datas;
let msg=`[${result.sessionId}:${result.flinkHost}:${result.flinkPort}] ${result.finishDate} ${result.success?'Success':'Error'}
[${result.time}ms] ${result.msg?result.msg:''} ${result.error?result.error:''} \r\n
Statement: ${result.statement}`;
return msg;
};
const saveSqlAndSettingToTask = async() => {
const fieldsValue = await form.validateFields();
if(current.task){
......@@ -97,7 +91,7 @@ const StudioMenu = (props: any) => {
const runMenu = (
<Menu>
<Menu.Item onClick={executeSql}>执行</Menu.Item>
<Menu.Item onClick={execute}>执行</Menu.Item>
</Menu>
);
......@@ -177,7 +171,7 @@ const StudioMenu = (props: any) => {
type="text"
icon={<PlayCircleTwoTone />}
//loading={loadings[2]}
onClick={executeSql}
onClick={execute}
/>
</Tooltip>
<Popconfirm
......@@ -188,12 +182,12 @@ const StudioMenu = (props: any) => {
cancelText="取消"
>
<Tooltip title="停止所有的 FlinkSql 任务">
<Badge size="small" count={1} offset={[-5, 5]}>
{/*<Badge size="small" count={1} offset={[-5, 5]}>*/}
<Button
type="text"
icon={<PauseCircleTwoTone />}
/>
</Badge>
{/*</Badge>*/}
</Tooltip>
</Popconfirm>
<Divider type="vertical" />
......@@ -220,4 +214,5 @@ export default connect(({Studio}: { Studio: StateType }) => ({
current: Studio.current,
currentPath: Studio.currentPath,
tabs: Studio.tabs,
monaco: Studio.monaco,
}))(StudioMenu);
......@@ -15,7 +15,7 @@ const StudioSetting = (props: any) => {
const getCluster = ()=>{
cluster.then(value=>{
cluster&&cluster.then(value=>{
let itemList = [];
for(let item of value){
let tag =(<><Tag color={item.enabled?"processing":"error"}>{item.type}</Tag>{item.alias}</>);
......@@ -134,6 +134,7 @@ const StudioSetting = (props: any) => {
className={styles.form_item}>
<Select
placeholder="选择会话"
defaultValue='admin'
dropdownRender={menu => (
<div>
{menu}
......
......@@ -34,40 +34,3 @@ export function convertToTreeData(data:TreeDataNode[], pid:number,path?:string[]
}
return result
}
export function getLeafFromTree(data:DataType[], arr:DataType[]) {
if (typeof arr == 'undefined') {
arr = [];
}
for (let i = 0; i < data.length; i++) {
let sonList = data[i].children;
if (sonList) {
if (sonList.length == 0) {
arr.push(data[i]);
} else {
getLeafFromTree(sonList, arr);
}
} else {
arr.push(data[i]);
}
}
return arr;
}
export function getChildFromTree(data:DataType[], arr:DataType[]) {
if (typeof arr == 'undefined') {
arr = [];
}
for (let i = 0; i < data.length; i++) {
if (data[i].isParent) {
let sonList = data[i].children;
if (!sonList || sonList == null || sonList.length == 0) {
} else {
getChildFromTree(sonList, arr);
}
} else {
arr.push(data[i]);
}
}
return arr;
}
......@@ -107,7 +107,11 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
key: node.taskId,
value:(result.datas.statement?result.datas.statement:''),
closable: true,
task:result.datas,
task:{
session:'admin',
maxRowNum: 100,
...result.datas
},
console:{
result:[],
}
......
......@@ -12,6 +12,7 @@ import {StateType} from "@/pages/FlinkSqlStudio/model";
import StudioConsole from "./StudioConsole";
import StudioSetting from "./StudioSetting";
import StudioEdit from "./StudioEdit";
import StudioConnector from "./StudioConnector";
const {TabPane} = Tabs;
......@@ -62,7 +63,7 @@ const Studio: React.FC<StudioProps> = ({sql}) => {
</Tabs>
<Tabs defaultActiveKey="1" size="small">
<TabPane tab={<span>&nbsp;<ApiOutlined />连接器</span>} key="1" >
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
<StudioConnector />
</TabPane>
<TabPane tab={<span>&nbsp;<DashboardOutlined />总览</span>} key="2" >
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
......
export default {
'pages.layouts.userLayout.title': 'Dlink 是一款开源的 Apache Flink 开发运维平台',
'pages.layouts.userLayout.title': 'Dlink 是一款开源的基于 Apache Flink 的实时计算平台',
'pages.login.accountLogin.tab': '账户密码登录',
'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)',
'pages.login.failure': '登录失败,请重试!',
......@@ -22,10 +22,12 @@ export default {
'pages.login.submit': '登录',
'pages.login.loginWith': '其他登录方式 :',
'pages.login.registerAccount': '注册账户',
'pages.welcome.advancedComponent': '高级表格',
'pages.welcome.link': '欢迎使用',
'pages.welcome.advancedLayout': '高级布局',
'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
'pages.welcome.Community': '官方社区',
'pages.welcome.upgrade': '更新日志',
'pages.welcome.link': '欢迎加入',
'pages.welcome.star': '欢迎 Star ',
'pages.welcome.advancedLayout': 'Github',
'pages.welcome.alertMessage': '实时计算平台 Dlink & Apache Flink 即将发布,目前为体验版,版本号为 0.1.0。',
'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看',
'pages.admin.subPage.alertMessage': 'umi ui 现已发布,欢迎使用 npm run ui 启动体验。',
'pages.searchTable.createForm.newRule': '新建规则',
......
......@@ -17,7 +17,7 @@ export default (): React.ReactNode => {
<Alert
message={intl.formatMessage({
id: 'pages.welcome.alertMessage',
defaultMessage: '更快更强的重型组件,已经发布。',
defaultMessage: '实时计算平台 Dlink & Apache Flink 即将发布,目前为体验版,版本号为0.1.0。',
})}
type="success"
showIcon
......
import React from 'react';
import { Modal } from 'antd';
type CreateFormProps = {
modalVisible: boolean;
onCancel: () => void;
};
const CreateForm: React.FC<CreateFormProps> = (props) => {
const { modalVisible, onCancel } = props;
return (
<Modal
destroyOnClose
title="添加 Flink 集群"
visible={modalVisible}
onCancel={() => onCancel()}
footer={null}
>
{props.children}
</Modal>
);
};
export default CreateForm;
import React, {useEffect, useState} from 'react';
import { Form, Button, Input, Modal,Select } from 'antd';
import Switch from "antd/es/switch";
import TextArea from "antd/es/input/TextArea";
import {DocumentTableListItem} from "@/pages/Document/data";
export type UpdateFormProps = {
onCancel: (flag?: boolean, formVals?: Partial<DocumentTableListItem>) => void;
onSubmit: (values: Partial<DocumentTableListItem>) => void;
updateModalVisible: boolean;
values: Partial<DocumentTableListItem>;
};
const FormItem = Form.Item;
const Option = Select.Option;
const formLayout = {
labelCol: { span: 7 },
wrapperCol: { span: 13 },
};
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const [formVals, setFormVals] = useState<Partial<DocumentTableListItem>>({
id: props.values.id,
name: props.values.name,
category: props.values.category,
type: props.values.type,
subtype: props.values.subtype,
description: props.values.description,
version: props.values.version,
likeNum: props.values.likeNum,
enabled: props.values.enabled,
});
const [form] = Form.useForm();
const {
onSubmit: handleUpdate,
onCancel: handleUpdateModalVisible,
updateModalVisible,
values,
} = props;
const submitForm = async () => {
const fieldsValue = await form.validateFields();
setFormVals({ ...formVals, ...fieldsValue });
handleUpdate({ ...formVals, ...fieldsValue });
};
const renderContent = (formVals) => {
return (
<>
<FormItem
name="name"
label="名称"
rules={[{ required: true, message: '请输入名称!' }]} >
<Input placeholder="请输入" />
</FormItem>
<FormItem
name="category"
label="文档类型"
>
<Select defaultValue="function" allowClear>
<Option value="function">函数</Option>
</Select>
</FormItem>
<FormItem
name="type"
label="类型"
>
<Select defaultValue="内置函数" allowClear>
<Option value="内置函数">内置函数</Option>
<Option value="UDF">UDF</Option>
</Select>
</FormItem>
<FormItem
name="subtype"
label="子类型"
>
<Select defaultValue="比较函数" allowClear>
<Option value="比较函数">比较函数</Option>
<Option value="逻辑函数">逻辑函数</Option>
<Option value="算术函数">算术函数</Option>
<Option value="字符串函数">字符串函数</Option>
<Option value="时间函数">时间函数</Option>
<Option value="条件函数">条件函数</Option>
<Option value="类型转换函数">类型转换函数</Option>
<Option value="Collection 函数">Collection 函数</Option>
<Option value="Value Collection 函数">Value Collection 函数</Option>
<Option value="Value Access 函数">Value Access 函数</Option>
<Option value="分组函数">分组函数</Option>
<Option value="hash函数">hash函数</Option>
<Option value="聚合函数">聚合函数</Option>
<Option value="列函数">列函数</Option>
<Option value="表值聚合函数">表值聚合函数</Option>
<Option value="其他函数">其他函数</Option>
</Select>
</FormItem>
<FormItem
name="description"
label="描述"
>
<TextArea placeholder="" allowClear autoSize={{ minRows: 3, maxRows: 10 }}/>
</FormItem>
<FormItem
name="version"
label="版本"
>
<Input placeholder="请输入" />
</FormItem>
<FormItem
name="enabled"
label="是否启用"
rules={[{ required: true, message: '请输入是否启用!' }]} >
<Switch checkedChildren="启用" unCheckedChildren="禁用"
defaultChecked={formVals.enabled}/>
</FormItem>
</>
);
};
const renderFooter = () => {
return (
<>
<Button onClick={() => handleUpdateModalVisible(false, values)}>取消</Button>
<Button type="primary" onClick={() => submitForm()}>
完成
</Button>
</>
);
};
return (
<Modal
width={640}
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose
title="编辑文档"
visible={updateModalVisible}
footer={renderFooter()}
onCancel={() => handleUpdateModalVisible()}
>
<Form
{...formLayout}
form={form}
initialValues={{
id: formVals.id,
name: formVals.name,
category: formVals.category,
type: formVals.type,
subtype: formVals.subtype,
description: formVals.description,
version: formVals.version,
likeNum: formVals.likeNum,
enabled: formVals.enabled,
}}
>
{renderContent(formVals)}
</Form>
</Modal>
);
};
export default UpdateForm;
export type DocumentTableListItem = {
id: number,
name: string,
category: string,
type: string,
subtype: string,
description: string,
version: string,
likeNum: number,
enabled: boolean,
createTime: Date,
updateTime: Date,
};
This diff is collapsed.
......@@ -60,6 +60,7 @@ export type StateType = {
cluster?:ClusterType[];
current: TabsItemType;
sql?: string;
monaco?: any;
currentPath?: string[];
tabs:TabsType;
session:string[];
......@@ -74,6 +75,7 @@ export type ModelType = {
reducers: {
saveSql: Reducer<StateType>;
saveCurrentPath: Reducer<StateType>;
saveMonaco: Reducer<StateType>;
saveTabs: Reducer<StateType>;
changeActiveKey: Reducer<StateType>;
saveTaskData: Reducer<StateType>;
......@@ -115,6 +117,7 @@ const Model: ModelType = {
}
},
sql: '',
monaco: {},
currentPath: [],
tabs:{
activeKey: 0,
......@@ -177,6 +180,14 @@ const Model: ModelType = {
currentPath:payload,
};
},
saveMonaco(state, { payload }) {
return {
...state,
monaco:{
...payload
},
};
},
saveTabs(state, { payload }) {
let newCurrent = state.current;
for(let i=0;i<payload.panes.length;i++){
......
......@@ -10,6 +10,15 @@ export async function executeSql(params: StudioParam) {
});
}
export async function executeDDL(params: StudioParam) {
return request<API.Result>('/api/studio/executeDDL', {
method: 'POST',
data: {
...params,
},
});
}
export async function getCatalogueTreeData(params?: StudioParam) {
return request<API.Result>('/api/catalogue/getCatalogueTreeData', {
method: 'POST',
......
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Card, Alert, Typography } from 'antd';
import { Card, Alert, Typography,Timeline } from 'antd';
import { useIntl, FormattedMessage } from 'umi';
import styles from './Welcome.less';
const { Text, Link,Paragraph } = Typography;
const CodePreview: React.FC = ({ children }) => (
<pre className={styles.pre}>
<code>
......@@ -31,32 +31,49 @@ export default (): React.ReactNode => {
}}
/>
<Typography.Text strong>
<FormattedMessage id="pages.welcome.advancedComponent" defaultMessage="高级表格" />{' '}
<a
href="https://procomponents.ant.design/components/table"
rel="noopener noreferrer"
target="__blank"
>
<FormattedMessage id="pages.welcome.link" defaultMessage="欢迎使用" />
</a>
<FormattedMessage id="pages.welcome.Community" defaultMessage="官方社区" />{' '}
<FormattedMessage id="pages.welcome.link" defaultMessage="欢迎加入" />
</Typography.Text>
<CodePreview>yarn add @ant-design/pro-table</CodePreview>
<CodePreview>微信公众号:Datalink数据中台</CodePreview>
<Typography.Text
strong
style={{
marginBottom: 12,
}}
>
<FormattedMessage id="pages.welcome.advancedLayout" defaultMessage="高级布局" />{' '}
<FormattedMessage id="pages.welcome.advancedLayout" defaultMessage="Github" />{' '}
<a
href="https://procomponents.ant.design/components/layout"
href="https://github.com/aiwenmo/dlink"
rel="noopener noreferrer"
target="__blank"
>
<FormattedMessage id="pages.welcome.link" defaultMessage="欢迎使用" />
<FormattedMessage id="pages.welcome.star" defaultMessage="欢迎 Star " />
</a>
</Typography.Text>
<CodePreview>yarn add @ant-design/pro-layout</CodePreview>
<Paragraph>
<Typography.Text strong>
<FormattedMessage id="pages.welcome.upgrade" defaultMessage="更新日志" />
</Typography.Text>
</Paragraph>
<p> </p>
<Timeline pending="Recording..." reverse={true}>
<Timeline.Item><Text code>0.1.0</Text> <Text type="secondary">2015-09-01</Text>
<p> </p>
<Paragraph>
<ul>
<li>
<Link href="">FlinkSql Studio</Link>
</li>
<li>
<Link href="">Flink 集群</Link>
</li>
<li>
<Link href="">Flink 任务</Link>
</li>
</ul>
</Paragraph>
</Timeline.Item>
</Timeline>
</Card>
</PageContainer>
);
......
......@@ -95,7 +95,7 @@ const Login: React.FC = () => {
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src="/logo.svg" />
<span className={styles.title}>Dlink</span>
<span className={styles.title}>Dlink & Apache Flink</span>
</Link>
</div>
<div className={styles.desc}>
......@@ -139,7 +139,7 @@ const Login: React.FC = () => {
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin or user',
defaultMessage: '用户名:',
})}
rules={[
{
......@@ -161,7 +161,7 @@ const Login: React.FC = () => {
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: ant.design',
defaultMessage: '密码:',
})}
rules={[
{
......
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