Commit 9bd1618c authored by godkaikai's avatar godkaikai

预览数据

parent 9cb959f9
...@@ -53,7 +53,6 @@ public class ClusterController { ...@@ -53,7 +53,6 @@ public class ClusterController {
@DeleteMapping @DeleteMapping
public Result deleteMul(@RequestBody JsonNode para) { public Result deleteMul(@RequestBody JsonNode para) {
if (para.size()>0){ if (para.size()>0){
boolean isAdmin = false;
List<Integer> error = new ArrayList<>(); List<Integer> error = new ArrayList<>();
for (final JsonNode item : para){ for (final JsonNode item : para){
Integer id = item.asInt(); Integer id = item.asInt();
...@@ -61,7 +60,7 @@ public class ClusterController { ...@@ -61,7 +60,7 @@ public class ClusterController {
error.add(id); error.add(id);
} }
} }
if(error.size()==0&&!isAdmin) { if(error.size()==0) {
return Result.succeed("删除成功"); return Result.succeed("删除成功");
}else { }else {
return Result.succeed("删除部分成功,但"+error.toString()+"删除失败,共"+error.size()+"次失败。"); 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; package com.dlink.controller;
import com.dlink.common.result.Result; import com.dlink.common.result.Result;
import com.dlink.dto.StudioDDLDTO;
import com.dlink.dto.StudioExecuteDTO; import com.dlink.dto.StudioExecuteDTO;
import com.dlink.model.Task; import com.dlink.model.Task;
import com.dlink.result.RunResult; import com.dlink.result.RunResult;
...@@ -34,4 +35,13 @@ public class StudioController { ...@@ -34,4 +35,13 @@ public class StudioController {
RunResult runResult = studioService.executeSql(studioExecuteDTO); RunResult runResult = studioService.executeSql(studioExecuteDTO);
return Result.succeed(runResult,"执行成功"); 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; package com.dlink.service;
import com.dlink.dto.StudioDDLDTO;
import com.dlink.dto.StudioExecuteDTO; import com.dlink.dto.StudioExecuteDTO;
import com.dlink.result.RunResult; import com.dlink.result.RunResult;
...@@ -11,4 +12,6 @@ import com.dlink.result.RunResult; ...@@ -11,4 +12,6 @@ import com.dlink.result.RunResult;
*/ */
public interface StudioService { public interface StudioService {
RunResult executeSql(StudioExecuteDTO studioExecuteDTO); 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; ...@@ -2,6 +2,7 @@ package com.dlink.service.impl;
import com.dlink.assertion.Assert; import com.dlink.assertion.Assert;
import com.dlink.cluster.FlinkCluster; import com.dlink.cluster.FlinkCluster;
import com.dlink.dto.StudioDDLDTO;
import com.dlink.dto.StudioExecuteDTO; import com.dlink.dto.StudioExecuteDTO;
import com.dlink.executor.Executor; import com.dlink.executor.Executor;
import com.dlink.executor.ExecutorSetting; import com.dlink.executor.ExecutorSetting;
...@@ -27,13 +28,38 @@ public class StudioServiceImpl implements StudioService { ...@@ -27,13 +28,38 @@ public class StudioServiceImpl implements StudioService {
@Override @Override
public RunResult executeSql(StudioExecuteDTO studioExecuteDTO) { public RunResult executeSql(StudioExecuteDTO studioExecuteDTO) {
studioExecuteDTO.setSession(studioExecuteDTO.getClusterId()+"_"+studioExecuteDTO.getSession());
String ExecuteType = Executor.REMOTE;
String host =null;
Cluster cluster = clusterService.getById(studioExecuteDTO.getClusterId()); Cluster cluster = clusterService.getById(studioExecuteDTO.getClusterId());
Assert.check(cluster); if(studioExecuteDTO.getClusterId()==0&&cluster==null){
String host = FlinkCluster.testFlinkJobManagerIP(cluster.getHosts(), cluster.getJobManagerHost()); ExecuteType = Executor.LOCAL;
Assert.checkHost(host); }else {
Assert.check(cluster);
host = FlinkCluster.testFlinkJobManagerIP(cluster.getHosts(), cluster.getJobManagerHost());
Assert.checkHost(host);
}
JobManager jobManager = new JobManager(host,studioExecuteDTO.getSession(),studioExecuteDTO.getMaxRowNum()); JobManager jobManager = new JobManager(host,studioExecuteDTO.getSession(),studioExecuteDTO.getMaxRowNum());
return jobManager.execute(studioExecuteDTO.getStatement(), new ExecutorSetting( return jobManager.execute(studioExecuteDTO.getStatement(), new ExecutorSetting(
Executor.REMOTE,studioExecuteDTO.getCheckPoint(),studioExecuteDTO.getParallelism(), ExecuteType,studioExecuteDTO.getCheckPoint(),studioExecuteDTO.getParallelism(),
studioExecuteDTO.isFragment(),studioExecuteDTO.getSavePointPath())); 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 { ...@@ -36,6 +36,10 @@ public class ExecutorSetting {
this.savePointPath = savePointPath; this.savePointPath = savePointPath;
} }
public boolean isRemote(){
return type.equals(Executor.REMOTE);
}
public String getType() { public String getType() {
return type; return type;
} }
......
...@@ -26,6 +26,12 @@ public class LocalStreamExecutor extends Executor { ...@@ -26,6 +26,12 @@ public class LocalStreamExecutor extends Executor {
public LocalStreamExecutor(ExecutorSetting executorSetting) { public LocalStreamExecutor(ExecutorSetting executorSetting) {
this.executorSetting = executorSetting; this.executorSetting = executorSetting;
this.environment = StreamExecutionEnvironment.createLocalEnvironment(); 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); stEnvironment = CustomTableEnvironmentImpl.create(environment);
if(executorSetting.isUseSqlFragment()){ if(executorSetting.isUseSqlFragment()){
stEnvironment.useSqlFragment(); stEnvironment.useSqlFragment();
......
...@@ -30,6 +30,12 @@ public class RemoteStreamExecutor extends Executor { ...@@ -30,6 +30,12 @@ public class RemoteStreamExecutor extends Executor {
this.executorSetting = executorSetting; this.executorSetting = executorSetting;
synchronized (RemoteStreamExecutor.class){ synchronized (RemoteStreamExecutor.class){
this.environment = StreamExecutionEnvironment.createRemoteEnvironment(environmentSetting.getHost(), environmentSetting.getPort()); 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){ if(stEnvironment == null){
stEnvironment = CustomTableEnvironmentImpl.create(environment); stEnvironment = CustomTableEnvironmentImpl.create(environment);
} }
......
...@@ -78,7 +78,11 @@ public class JobManager { ...@@ -78,7 +78,11 @@ public class JobManager {
if (executorEntity != null) { if (executorEntity != null) {
executor = executorEntity.getExecutor(); executor = executorEntity.getExecutor();
} else { } 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)); SessionPool.push(new ExecutorEntity(sessionId, executor));
} }
String[] Statements = statement.split(";"); String[] Statements = statement.split(";");
...@@ -99,18 +103,23 @@ public class JobManager { ...@@ -99,18 +103,23 @@ public class JobManager {
runResult.setResult(result); runResult.setResult(result);
runResult.setTime(timeElapsed); runResult.setTime(timeElapsed);
runResult.setFinishDate(LocalDateTime.now()); 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
StackTraceElement[] trace = e.getStackTrace(); StackTraceElement[] trace = e.getStackTrace();
StringBuffer resMsg = new StringBuffer(""); StringBuffer resMsg = new StringBuffer("");
for (StackTraceElement s : trace) { for (StackTraceElement s : trace) {
resMsg.append(" </br> " + s + " "); resMsg.append(" \n " + s + " ");
} }
runResult.setFinishDate(LocalDateTime.now()); runResult.setFinishDate(LocalDateTime.now());
runResult.setSuccess(false); 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;
} }
return runResult; return runResult;
......
...@@ -12,6 +12,7 @@ import java.time.LocalDateTime; ...@@ -12,6 +12,7 @@ import java.time.LocalDateTime;
**/ **/
public class RunResult { public class RunResult {
private String sessionId; private String sessionId;
private String jobId;
private String statement; private String statement;
private String flinkHost; private String flinkHost;
private Integer flinkPort; private Integer flinkPort;
...@@ -34,6 +35,14 @@ public class RunResult { ...@@ -34,6 +35,14 @@ public class RunResult {
this.setting = setting; this.setting = setting;
} }
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public ExecutorSetting getSetting() { public ExecutorSetting getSetting() {
return setting; return setting;
} }
......
This diff is collapsed.
...@@ -45,7 +45,7 @@ export default [ ...@@ -45,7 +45,7 @@ export default [
icon: 'cluster', icon: 'cluster',
component: './Cluster', component: './Cluster',
}, },
{ /*{
path: '/dev', path: '/dev',
name: 'dev', name: 'dev',
icon: 'crown', icon: 'crown',
...@@ -78,7 +78,7 @@ export default [ ...@@ -78,7 +78,7 @@ export default [
], ],
}, },
], ],
}, },*/
/*{ /*{
path: '/admin', path: '/admin',
name: 'admin', name: 'admin',
...@@ -94,7 +94,7 @@ export default [ ...@@ -94,7 +94,7 @@ export default [
}, },
], ],
},*/ },*/
{ /*{
path: '/demo', path: '/demo',
name: 'demo', name: 'demo',
icon: 'crown', icon: 'crown',
...@@ -139,7 +139,7 @@ export default [ ...@@ -139,7 +139,7 @@ export default [
], ],
}, },
], ],
}, },*/
{ {
path: '/', path: '/',
redirect: '/welcome', 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 {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi"; import {connect} from "umi";
...@@ -10,21 +10,27 @@ const StudioMsg = (props:any) => { ...@@ -10,21 +10,27 @@ const StudioMsg = (props:any) => {
return ( return (
<Typography> <Typography>
{current.console.result.map((item)=> { {current.console.result.map((item,index)=> {
return (<Paragraph> if(index==0) {
<blockquote><Link href={`http://${item.flinkHost}:${item.flinkPort}`} target="_blank"> return (<Paragraph>
[{item.sessionId}:{item.flinkHost}:{item.flinkPort}] <blockquote><Link href={`http://${item.flinkHost}:${item.flinkPort}`} target="_blank">
</Link> <Divider type="vertical" />{item.finishDate} [{item.sessionId}:{item.flinkHost}:{item.flinkPort}]
<Divider type="vertical" /> </Link> <Divider type="vertical"/>{item.finishDate}
{!item.success ? <><Badge status="error"/><Text type="danger">Error</Text></> : <Divider type="vertical"/>
<><Badge status="success"/><Text type="success">Success</Text></>} {!item.success ? <><Badge status="error"/><Text type="danger">Error</Text></> :
<Divider type="vertical" /> <><Badge status="success"/><Text type="success">Success</Text></>}
<Text keyboard>{item.time}ms</Text></blockquote> <Divider type="vertical"/>
{item.statement && (<pre style={{height:'40px'}}>{item.statement}</pre>)} {item.jobId&&<Text code>{item.jobId}</Text>}
{item.msg ? item.msg : ''} <Text keyboard>{item.time}ms</Text></blockquote>
{item.error && (<pre style={{height:'100px'}}>{item.error}</pre>)} {item.statement && (<pre style={{height: '100px'}}>{item.statement}</pre>)}
</Paragraph>) {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> </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 {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi"; import {connect} from "umi";
import {useState} from "react";
// import Highlighter from 'react-highlight-words';
import { SearchOutlined } from '@ant-design/icons';
const { Option } = Select; const { Option } = Select;
const { Title, Paragraph, Text, Link } = Typography; const { Title, Paragraph, Text, Link } = Typography;
...@@ -9,16 +12,101 @@ const { Title, Paragraph, Text, Link } = Typography; ...@@ -9,16 +12,101 @@ const { Title, Paragraph, Text, Link } = Typography;
const StudioTable = (props:any) => { const StudioTable = (props:any) => {
const {current} = props; 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 ( return (
<Typography> <Typography>
<Form.Item label="当前执行记录" tooltip="选择最近的执行记录,仅包含成功的记录"> <Form.Item label="当前执行记录" tooltip="选择最近的执行记录,仅包含成功的记录">
<Select <Select
//mode="multiple"
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder="选择最近的执行记录" placeholder="选择最近的执行记录"
defaultValue={[0]}
optionLabelProp="label" optionLabelProp="label"
onChange={onChange}
> >
{current.console.result.map((item,index)=> { {current.console.result.map((item,index)=> {
if(item.success) { if(item.success) {
...@@ -31,8 +119,9 @@ const StudioTable = (props:any) => { ...@@ -31,8 +119,9 @@ const StudioTable = (props:any) => {
</Option>) </Option>)
} }
})} })}
</Select> </Select>
</Form.Item> </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> </Typography>
); );
}; };
......
...@@ -6,6 +6,8 @@ import {connect} from "umi"; ...@@ -6,6 +6,8 @@ import {connect} from "umi";
import styles from "./index.less"; import styles from "./index.less";
import StudioMsg from "./StudioMsg"; import StudioMsg from "./StudioMsg";
import StudioTable from "./StudioTable"; import StudioTable from "./StudioTable";
import StudioHistory from "./StudioHistory";
import StudioDocument from "./StudioDocument";
const { TabPane } = Tabs; const { TabPane } = Tabs;
...@@ -68,7 +70,7 @@ const StudioConsole = (props:any) => { ...@@ -68,7 +70,7 @@ const StudioConsole = (props:any) => {
} }
key="5" key="5"
> >
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <StudioHistory />
</TabPane> </TabPane>
<TabPane <TabPane
tab={ tab={
...@@ -79,7 +81,7 @@ const StudioConsole = (props:any) => { ...@@ -79,7 +81,7 @@ const StudioConsole = (props:any) => {
} }
key="6" key="6"
> >
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <StudioDocument />
</TabPane> </TabPane>
<TabPane <TabPane
tab={ tab={
......
...@@ -8,7 +8,42 @@ const Completion =[ ...@@ -8,7 +8,42 @@ const Completion =[
insertText: 'SUM(${1:})', insertText: 'SUM(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的求和' 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; export default Completion;
...@@ -31,4 +31,11 @@ export type CompletionItem ={ ...@@ -31,4 +31,11 @@ export type CompletionItem ={
} }
export type StudioParam = { export type StudioParam = {
statement:string, statement:string,
checkPoint?: number,
savePointPath?: string,
parallelism?: number,
fragment?: boolean,
clusterId: number,
session:string,
maxRowNum?: number,
} }
...@@ -29,10 +29,9 @@ const FlinkSqlEditor = (props:any) => { ...@@ -29,10 +29,9 @@ const FlinkSqlEditor = (props:any) => {
selectOnLineNumbers: true, selectOnLineNumbers: true,
renderSideBySide: false, renderSideBySide: false,
}, },
sql=props.current.value,
current=props.current, current=props.current,
// sql,
dispatch, dispatch,
monaco,
} = props } = props
; ;
...@@ -48,6 +47,7 @@ const FlinkSqlEditor = (props:any) => { ...@@ -48,6 +47,7 @@ const FlinkSqlEditor = (props:any) => {
const cache: any = useRef(code.current); const cache: any = useRef(code.current);
const [refresh, setRefresh] = React.useState<boolean>(false); const [refresh, setRefresh] = React.useState<boolean>(false);
const [selectValue, setSelectValue] = React.useState<string>("");
useEffect( useEffect(
() => () => { () => () => {
...@@ -113,7 +113,6 @@ const FlinkSqlEditor = (props:any) => { ...@@ -113,7 +113,6 @@ const FlinkSqlEditor = (props:any) => {
} }
}); });
code.current = newSecondRightFields; // 数组长度永远为1 code.current = newSecondRightFields; // 数组长度永远为1
// 提示项设值 // 提示项设值
provider = monaco.languages.registerCompletionItemProvider('sql', { provider = monaco.languages.registerCompletionItemProvider('sql', {
provideCompletionItems() { provideCompletionItems() {
...@@ -149,6 +148,7 @@ const FlinkSqlEditor = (props:any) => { ...@@ -149,6 +148,7 @@ const FlinkSqlEditor = (props:any) => {
return ( return (
<React.Fragment> <React.Fragment>
<MonacoEditor <MonacoEditor
ref={monaco}
width={width} width={width}
height={height} height={height}
language={language} language={language}
...@@ -158,6 +158,7 @@ return ( ...@@ -158,6 +158,7 @@ return (
theme="vs-dark" theme="vs-dark"
editorDidMount={editorDidMountHandle} editorDidMount={editorDidMountHandle}
/> />
{selectValue}
</React.Fragment> </React.Fragment>
); );
}; };
...@@ -166,4 +167,5 @@ export default connect(({ Studio }: { Studio: StateType }) => ({ ...@@ -166,4 +167,5 @@ export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current, current: Studio.current,
sql: Studio.sql, sql: Studio.sql,
tabs: Studio.tabs, tabs: Studio.tabs,
monaco: Studio.monaco,
}))(FlinkSqlEditor); }))(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 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, 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 Space from "antd/es/space";
import Divider from "antd/es/divider"; import Divider from "antd/es/divider";
import Button from "antd/es/button/button"; import Button from "antd/es/button/button";
import Breadcrumb from "antd/es/breadcrumb/Breadcrumb"; import Breadcrumb from "antd/es/breadcrumb/Breadcrumb";
import {StateType} from "@/pages/FlinkSqlStudio/model"; import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi"; import {connect} from "umi";
import {useEffect, useState} from "react"; import { postAll} from "@/components/Common/crud";
import {handleAddOrUpdate, postAll} from "@/components/Common/crud"; import {executeSql} from "@/pages/FlinkSqlStudio/service";
const {SubMenu} = Menu;
//<Button shape="circle" icon={<CaretRightOutlined />} />
const menu = ( const menu = (
<Menu> <Menu>
<Menu.Item>1st menu item</Menu.Item> <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> </Menu>
); );
const StudioMenu = (props: any) => { const StudioMenu = (props: any) => {
const {tabs,current,currentPath,form,dispatch} = props; const {tabs,current,currentPath,form,dispatch,monaco} = props;
const [pathItem, setPathItem] = useState<[]>();
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 ={ let param ={
session:current.task.session, session:current.task.session,
statement:current.value, statement:selectsql,
clusterId:current.task.clusterId, clusterId:current.task.clusterId,
checkPoint:current.task.checkPoint, checkPoint:current.task.checkPoint,
parallelism:current.task.parallelism, parallelism:current.task.parallelism,
...@@ -46,8 +40,17 @@ const StudioMenu = (props: any) => { ...@@ -46,8 +40,17 @@ const StudioMenu = (props: any) => {
savePointPath:current.task.savePointPath, savePointPath:current.task.savePointPath,
}; };
const key = current.key; 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=>{ result.then(res=>{
notification.close(taskKey);
let newTabs = tabs; let newTabs = tabs;
for(let i=0;i<newTabs.panes.length;i++){ for(let i=0;i<newTabs.panes.length;i++){
if(newTabs.panes[i].key==key){ if(newTabs.panes[i].key==key){
...@@ -59,7 +62,6 @@ const StudioMenu = (props: any) => { ...@@ -59,7 +62,6 @@ const StudioMenu = (props: any) => {
break; break;
} }
} }
console.log(newTabs);
dispatch&&dispatch({ dispatch&&dispatch({
type: "Studio/saveTabs", type: "Studio/saveTabs",
payload: newTabs, payload: newTabs,
...@@ -67,14 +69,6 @@ const StudioMenu = (props: any) => { ...@@ -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 saveSqlAndSettingToTask = async() => {
const fieldsValue = await form.validateFields(); const fieldsValue = await form.validateFields();
if(current.task){ if(current.task){
...@@ -97,7 +91,7 @@ const StudioMenu = (props: any) => { ...@@ -97,7 +91,7 @@ const StudioMenu = (props: any) => {
const runMenu = ( const runMenu = (
<Menu> <Menu>
<Menu.Item onClick={executeSql}>执行</Menu.Item> <Menu.Item onClick={execute}>执行</Menu.Item>
</Menu> </Menu>
); );
...@@ -177,7 +171,7 @@ const StudioMenu = (props: any) => { ...@@ -177,7 +171,7 @@ const StudioMenu = (props: any) => {
type="text" type="text"
icon={<PlayCircleTwoTone />} icon={<PlayCircleTwoTone />}
//loading={loadings[2]} //loading={loadings[2]}
onClick={executeSql} onClick={execute}
/> />
</Tooltip> </Tooltip>
<Popconfirm <Popconfirm
...@@ -188,12 +182,12 @@ const StudioMenu = (props: any) => { ...@@ -188,12 +182,12 @@ const StudioMenu = (props: any) => {
cancelText="取消" cancelText="取消"
> >
<Tooltip title="停止所有的 FlinkSql 任务"> <Tooltip title="停止所有的 FlinkSql 任务">
<Badge size="small" count={1} offset={[-5, 5]}> {/*<Badge size="small" count={1} offset={[-5, 5]}>*/}
<Button <Button
type="text" type="text"
icon={<PauseCircleTwoTone />} icon={<PauseCircleTwoTone />}
/> />
</Badge> {/*</Badge>*/}
</Tooltip> </Tooltip>
</Popconfirm> </Popconfirm>
<Divider type="vertical" /> <Divider type="vertical" />
...@@ -220,4 +214,5 @@ export default connect(({Studio}: { Studio: StateType }) => ({ ...@@ -220,4 +214,5 @@ export default connect(({Studio}: { Studio: StateType }) => ({
current: Studio.current, current: Studio.current,
currentPath: Studio.currentPath, currentPath: Studio.currentPath,
tabs: Studio.tabs, tabs: Studio.tabs,
monaco: Studio.monaco,
}))(StudioMenu); }))(StudioMenu);
...@@ -15,7 +15,7 @@ const StudioSetting = (props: any) => { ...@@ -15,7 +15,7 @@ const StudioSetting = (props: any) => {
const getCluster = ()=>{ const getCluster = ()=>{
cluster.then(value=>{ cluster&&cluster.then(value=>{
let itemList = []; let itemList = [];
for(let item of value){ for(let item of value){
let tag =(<><Tag color={item.enabled?"processing":"error"}>{item.type}</Tag>{item.alias}</>); let tag =(<><Tag color={item.enabled?"processing":"error"}>{item.type}</Tag>{item.alias}</>);
...@@ -134,6 +134,7 @@ const StudioSetting = (props: any) => { ...@@ -134,6 +134,7 @@ const StudioSetting = (props: any) => {
className={styles.form_item}> className={styles.form_item}>
<Select <Select
placeholder="选择会话" placeholder="选择会话"
defaultValue='admin'
dropdownRender={menu => ( dropdownRender={menu => (
<div> <div>
{menu} {menu}
......
...@@ -34,40 +34,3 @@ export function convertToTreeData(data:TreeDataNode[], pid:number,path?:string[] ...@@ -34,40 +34,3 @@ export function convertToTreeData(data:TreeDataNode[], pid:number,path?:string[]
} }
return result 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) => { ...@@ -107,7 +107,11 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
key: node.taskId, key: node.taskId,
value:(result.datas.statement?result.datas.statement:''), value:(result.datas.statement?result.datas.statement:''),
closable: true, closable: true,
task:result.datas, task:{
session:'admin',
maxRowNum: 100,
...result.datas
},
console:{ console:{
result:[], result:[],
} }
......
...@@ -12,6 +12,7 @@ import {StateType} from "@/pages/FlinkSqlStudio/model"; ...@@ -12,6 +12,7 @@ import {StateType} from "@/pages/FlinkSqlStudio/model";
import StudioConsole from "./StudioConsole"; import StudioConsole from "./StudioConsole";
import StudioSetting from "./StudioSetting"; import StudioSetting from "./StudioSetting";
import StudioEdit from "./StudioEdit"; import StudioEdit from "./StudioEdit";
import StudioConnector from "./StudioConnector";
const {TabPane} = Tabs; const {TabPane} = Tabs;
...@@ -62,7 +63,7 @@ const Studio: React.FC<StudioProps> = ({sql}) => { ...@@ -62,7 +63,7 @@ const Studio: React.FC<StudioProps> = ({sql}) => {
</Tabs> </Tabs>
<Tabs defaultActiveKey="1" size="small"> <Tabs defaultActiveKey="1" size="small">
<TabPane tab={<span>&nbsp;<ApiOutlined />连接器</span>} key="1" > <TabPane tab={<span>&nbsp;<ApiOutlined />连接器</span>} key="1" >
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <StudioConnector />
</TabPane> </TabPane>
<TabPane tab={<span>&nbsp;<DashboardOutlined />总览</span>} key="2" > <TabPane tab={<span>&nbsp;<DashboardOutlined />总览</span>} key="2" >
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
......
export default { export default {
'pages.layouts.userLayout.title': 'Dlink 是一款开源的 Apache Flink 开发运维平台', 'pages.layouts.userLayout.title': 'Dlink 是一款开源的基于 Apache Flink 的实时计算平台',
'pages.login.accountLogin.tab': '账户密码登录', 'pages.login.accountLogin.tab': '账户密码登录',
'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)', 'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)',
'pages.login.failure': '登录失败,请重试!', 'pages.login.failure': '登录失败,请重试!',
...@@ -22,10 +22,12 @@ export default { ...@@ -22,10 +22,12 @@ export default {
'pages.login.submit': '登录', 'pages.login.submit': '登录',
'pages.login.loginWith': '其他登录方式 :', 'pages.login.loginWith': '其他登录方式 :',
'pages.login.registerAccount': '注册账户', 'pages.login.registerAccount': '注册账户',
'pages.welcome.advancedComponent': '高级表格', 'pages.welcome.Community': '官方社区',
'pages.welcome.link': '欢迎使用', 'pages.welcome.upgrade': '更新日志',
'pages.welcome.advancedLayout': '高级布局', 'pages.welcome.link': '欢迎加入',
'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。', '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.title': ' 这个页面只有 admin 权限才能查看',
'pages.admin.subPage.alertMessage': 'umi ui 现已发布,欢迎使用 npm run ui 启动体验。', 'pages.admin.subPage.alertMessage': 'umi ui 现已发布,欢迎使用 npm run ui 启动体验。',
'pages.searchTable.createForm.newRule': '新建规则', 'pages.searchTable.createForm.newRule': '新建规则',
......
...@@ -17,7 +17,7 @@ export default (): React.ReactNode => { ...@@ -17,7 +17,7 @@ export default (): React.ReactNode => {
<Alert <Alert
message={intl.formatMessage({ message={intl.formatMessage({
id: 'pages.welcome.alertMessage', id: 'pages.welcome.alertMessage',
defaultMessage: '更快更强的重型组件,已经发布。', defaultMessage: '实时计算平台 Dlink & Apache Flink 即将发布,目前为体验版,版本号为0.1.0。',
})} })}
type="success" type="success"
showIcon 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 = { ...@@ -60,6 +60,7 @@ export type StateType = {
cluster?:ClusterType[]; cluster?:ClusterType[];
current: TabsItemType; current: TabsItemType;
sql?: string; sql?: string;
monaco?: any;
currentPath?: string[]; currentPath?: string[];
tabs:TabsType; tabs:TabsType;
session:string[]; session:string[];
...@@ -74,6 +75,7 @@ export type ModelType = { ...@@ -74,6 +75,7 @@ export type ModelType = {
reducers: { reducers: {
saveSql: Reducer<StateType>; saveSql: Reducer<StateType>;
saveCurrentPath: Reducer<StateType>; saveCurrentPath: Reducer<StateType>;
saveMonaco: Reducer<StateType>;
saveTabs: Reducer<StateType>; saveTabs: Reducer<StateType>;
changeActiveKey: Reducer<StateType>; changeActiveKey: Reducer<StateType>;
saveTaskData: Reducer<StateType>; saveTaskData: Reducer<StateType>;
...@@ -115,6 +117,7 @@ const Model: ModelType = { ...@@ -115,6 +117,7 @@ const Model: ModelType = {
} }
}, },
sql: '', sql: '',
monaco: {},
currentPath: [], currentPath: [],
tabs:{ tabs:{
activeKey: 0, activeKey: 0,
...@@ -177,6 +180,14 @@ const Model: ModelType = { ...@@ -177,6 +180,14 @@ const Model: ModelType = {
currentPath:payload, currentPath:payload,
}; };
}, },
saveMonaco(state, { payload }) {
return {
...state,
monaco:{
...payload
},
};
},
saveTabs(state, { payload }) { saveTabs(state, { payload }) {
let newCurrent = state.current; let newCurrent = state.current;
for(let i=0;i<payload.panes.length;i++){ for(let i=0;i<payload.panes.length;i++){
......
...@@ -10,6 +10,15 @@ export async function executeSql(params: StudioParam) { ...@@ -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) { export async function getCatalogueTreeData(params?: StudioParam) {
return request<API.Result>('/api/catalogue/getCatalogueTreeData', { return request<API.Result>('/api/catalogue/getCatalogueTreeData', {
method: 'POST', method: 'POST',
......
import React from 'react'; import React from 'react';
import { PageContainer } from '@ant-design/pro-layout'; 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 { useIntl, FormattedMessage } from 'umi';
import styles from './Welcome.less'; import styles from './Welcome.less';
const { Text, Link,Paragraph } = Typography;
const CodePreview: React.FC = ({ children }) => ( const CodePreview: React.FC = ({ children }) => (
<pre className={styles.pre}> <pre className={styles.pre}>
<code> <code>
...@@ -31,32 +31,49 @@ export default (): React.ReactNode => { ...@@ -31,32 +31,49 @@ export default (): React.ReactNode => {
}} }}
/> />
<Typography.Text strong> <Typography.Text strong>
<FormattedMessage id="pages.welcome.advancedComponent" defaultMessage="高级表格" />{' '} <FormattedMessage id="pages.welcome.Community" defaultMessage="官方社区" />{' '}
<a <FormattedMessage id="pages.welcome.link" defaultMessage="欢迎加入" />
href="https://procomponents.ant.design/components/table"
rel="noopener noreferrer"
target="__blank"
>
<FormattedMessage id="pages.welcome.link" defaultMessage="欢迎使用" />
</a>
</Typography.Text> </Typography.Text>
<CodePreview>yarn add @ant-design/pro-table</CodePreview> <CodePreview>微信公众号:Datalink数据中台</CodePreview>
<Typography.Text <Typography.Text
strong strong
style={{ style={{
marginBottom: 12, marginBottom: 12,
}} }}
> >
<FormattedMessage id="pages.welcome.advancedLayout" defaultMessage="高级布局" />{' '} <FormattedMessage id="pages.welcome.advancedLayout" defaultMessage="Github" />{' '}
<a <a
href="https://procomponents.ant.design/components/layout" href="https://github.com/aiwenmo/dlink"
rel="noopener noreferrer" rel="noopener noreferrer"
target="__blank" target="__blank"
> >
<FormattedMessage id="pages.welcome.link" defaultMessage="欢迎使用" /> <FormattedMessage id="pages.welcome.star" defaultMessage="欢迎 Star " />
</a> </a>
</Typography.Text> </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> </Card>
</PageContainer> </PageContainer>
); );
......
...@@ -95,7 +95,7 @@ const Login: React.FC = () => { ...@@ -95,7 +95,7 @@ const Login: React.FC = () => {
<div className={styles.header}> <div className={styles.header}>
<Link to="/"> <Link to="/">
<img alt="logo" className={styles.logo} src="/logo.svg" /> <img alt="logo" className={styles.logo} src="/logo.svg" />
<span className={styles.title}>Dlink</span> <span className={styles.title}>Dlink & Apache Flink</span>
</Link> </Link>
</div> </div>
<div className={styles.desc}> <div className={styles.desc}>
...@@ -139,7 +139,7 @@ const Login: React.FC = () => { ...@@ -139,7 +139,7 @@ const Login: React.FC = () => {
}} }}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder', id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin or user', defaultMessage: '用户名:',
})} })}
rules={[ rules={[
{ {
...@@ -161,7 +161,7 @@ const Login: React.FC = () => { ...@@ -161,7 +161,7 @@ const Login: React.FC = () => {
}} }}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder', id: 'pages.login.password.placeholder',
defaultMessage: '密码: ant.design', defaultMessage: '密码:',
})} })}
rules={[ 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