Unverified Commit ab68f5d2 authored by aiwenmo's avatar aiwenmo Committed by GitHub

[Feature-1119][admin,web,process] Add process console info (#1120)

Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>
parent 0305d3f8
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.dlink.controller; package com.dlink.controller;
import com.dlink.common.result.ProTableResult; import com.dlink.common.result.ProTableResult;
import com.dlink.common.result.Result;
import com.dlink.process.model.ProcessEntity; import com.dlink.process.model.ProcessEntity;
import com.dlink.service.ProcessService; import com.dlink.service.ProcessService;
...@@ -31,6 +32,8 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -31,6 +32,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.SaManager;
/** /**
* ProcessController * ProcessController
* *
...@@ -49,4 +52,9 @@ public class ProcessController { ...@@ -49,4 +52,9 @@ public class ProcessController {
List<ProcessEntity> processEntities = processService.listAllProcess(active); List<ProcessEntity> processEntities = processService.listAllProcess(active);
return ProTableResult.<ProcessEntity>builder().success(true).data(processEntities).build(); return ProTableResult.<ProcessEntity>builder().success(true).data(processEntities).build();
} }
@GetMapping("/getConsoleByUserId")
public Result<String> getConsoleByUserId() {
return Result.data(processService.getConsoleByUserId(SaManager.getStpLogic(null).getLoginIdAsInt()));
}
} }
...@@ -32,4 +32,6 @@ import java.util.List; ...@@ -32,4 +32,6 @@ import java.util.List;
public interface ProcessService { public interface ProcessService {
List<ProcessEntity> listAllProcess(boolean active); List<ProcessEntity> listAllProcess(boolean active);
String getConsoleByUserId(Integer userId);
} }
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.dlink.service.impl; package com.dlink.service.impl;
import com.dlink.process.model.ProcessEntity; import com.dlink.process.model.ProcessEntity;
import com.dlink.process.pool.ConsolePool;
import com.dlink.process.pool.ProcessPool; import com.dlink.process.pool.ProcessPool;
import com.dlink.service.ProcessService; import com.dlink.service.ProcessService;
...@@ -49,4 +50,14 @@ public class ProcessServiceImpl implements ProcessService { ...@@ -49,4 +50,14 @@ public class ProcessServiceImpl implements ProcessService {
return processEntityMap.values().stream().sorted(Comparator.comparing(ProcessEntity::getStartTime).reversed()) return processEntityMap.values().stream().sorted(Comparator.comparing(ProcessEntity::getStartTime).reversed())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override
public String getConsoleByUserId(Integer userId) {
String user = userId.toString();
if (ConsolePool.getInstance().exist(user)) {
return ConsolePool.getInstance().get(user).toString();
} else {
return "";
}
}
} }
...@@ -50,6 +50,7 @@ import com.dlink.model.Task; ...@@ -50,6 +50,7 @@ import com.dlink.model.Task;
import com.dlink.process.context.ProcessContextHolder; import com.dlink.process.context.ProcessContextHolder;
import com.dlink.process.model.ProcessEntity; import com.dlink.process.model.ProcessEntity;
import com.dlink.process.model.ProcessType; import com.dlink.process.model.ProcessType;
import com.dlink.process.pool.ConsolePool;
import com.dlink.process.pool.ProcessPool; import com.dlink.process.pool.ProcessPool;
import com.dlink.result.DDLResult; import com.dlink.result.DDLResult;
import com.dlink.result.IResult; import com.dlink.result.IResult;
...@@ -85,6 +86,8 @@ import com.fasterxml.jackson.databind.JsonNode; ...@@ -85,6 +86,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import cn.dev33.satoken.SaManager;
/** /**
* StudioServiceImpl * StudioServiceImpl
* *
...@@ -243,8 +246,9 @@ public class StudioServiceImpl implements StudioService { ...@@ -243,8 +246,9 @@ public class StudioServiceImpl implements StudioService {
private List<SqlExplainResult> explainFlinkSql(StudioExecuteDTO studioExecuteDTO) { private List<SqlExplainResult> explainFlinkSql(StudioExecuteDTO studioExecuteDTO) {
Map<String, ProcessEntity> map = ProcessPool.getInstance().getMap(); Map<String, ProcessEntity> map = ProcessPool.getInstance().getMap();
Map<String, StringBuilder> map2 = ConsolePool.getInstance().getMap();
ProcessEntity process = ProcessContextHolder.registerProcess( ProcessEntity process = ProcessContextHolder.registerProcess(
ProcessEntity.init(ProcessType.FLINKEXPLAIN, 1, "admin")); ProcessEntity.init(ProcessType.FLINKEXPLAIN, SaManager.getStpLogic(null).getLoginIdAsInt(), "admin"));
addFlinkSQLEnv(studioExecuteDTO); addFlinkSQLEnv(studioExecuteDTO);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.dlink.process.model; package com.dlink.process.model;
import com.dlink.assertion.Asserts; import com.dlink.assertion.Asserts;
import com.dlink.process.pool.ConsolePool;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -120,16 +121,18 @@ public class ProcessEntity { ...@@ -120,16 +121,18 @@ public class ProcessEntity {
if (isNullProcess()) { if (isNullProcess()) {
return; return;
} }
steps.get(stepIndex - 1).appendInfo( String message = StrUtil.format("\n[{}] {} CONFIG: {}", type.getValue(), LocalDateTime.now(), str);
StrUtil.format("\n[{}] {} CONFIG: {}", type.getValue(), LocalDateTime.now(), str)); steps.get(stepIndex - 1).appendInfo(message);
ConsolePool.write(message, userId);
} }
public void info(String str) { public void info(String str) {
if (isNullProcess()) { if (isNullProcess()) {
return; return;
} }
steps.get(stepIndex - 1).appendInfo( String message = StrUtil.format("\n[{}] {} INFO: {}", type.getValue(), LocalDateTime.now(), str);
StrUtil.format("\n[{}] {} INFO: {}", type.getValue(), LocalDateTime.now(), str)); steps.get(stepIndex - 1).appendInfo(message);
ConsolePool.write(message, userId);
} }
public void infoSuccess() { public void infoSuccess() {
...@@ -137,6 +140,7 @@ public class ProcessEntity { ...@@ -137,6 +140,7 @@ public class ProcessEntity {
return; return;
} }
steps.get(stepIndex - 1).appendInfo("Success."); steps.get(stepIndex - 1).appendInfo("Success.");
ConsolePool.write("Success.", userId);
} }
public void infoFail() { public void infoFail() {
...@@ -144,16 +148,17 @@ public class ProcessEntity { ...@@ -144,16 +148,17 @@ public class ProcessEntity {
return; return;
} }
steps.get(stepIndex - 1).appendInfo("Fail."); steps.get(stepIndex - 1).appendInfo("Fail.");
ConsolePool.write("Fail.", userId);
} }
public void error(String str) { public void error(String str) {
if (isNullProcess()) { if (isNullProcess()) {
return; return;
} }
steps.get(stepIndex - 1).appendInfo( String message = StrUtil.format("\n[{}] {} ERROR: {}", type.getValue(), LocalDateTime.now(), str);
StrUtil.format("\n[{}] {} ERROR: {}", type.getValue(), LocalDateTime.now(), str)); steps.get(stepIndex - 1).appendInfo(message);
steps.get(stepIndex - 1).appendError( steps.get(stepIndex - 1).appendError(message);
StrUtil.format("\n[{}] {} ERROR: {}", type.getValue(), LocalDateTime.now(), str)); ConsolePool.write(message, userId);
} }
public void nextStep() { public void nextStep() {
......
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.dlink.process.pool;
import com.dlink.pool.AbstractPool;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* ConsolePool
*
* @author wenmo
* @since 2022/10/18 22:51
*/
public class ConsolePool extends AbstractPool<StringBuilder> {
private static volatile Map<String, StringBuilder> consoleEntityMap = new ConcurrentHashMap<>();
private static ConsolePool instance = new ConsolePool();
public static ConsolePool getInstance() {
return instance;
}
@Override
public Map<String, StringBuilder> getMap() {
return consoleEntityMap;
}
@Override
public void refresh(StringBuilder entity) {
}
public static void write(String str, Integer userId) {
String user = userId.toString();
if (consoleEntityMap.containsKey(user)) {
consoleEntityMap.get(user).append(str);
} else {
consoleEntityMap.put(user, new StringBuilder(str));
}
}
}
...@@ -17,125 +17,37 @@ ...@@ -17,125 +17,37 @@
* *
*/ */
import {Badge, Button, Divider, Empty, Modal, Tag, Typography} from "antd";
import {StateType} from "@/pages/DataStudio/model"; import {StateType} from "@/pages/DataStudio/model";
import {connect} from "umi"; import {connect} from "umi";
import {FireOutlined, ZoomInOutlined} from '@ant-design/icons'; import React, {useEffect, useState} from "react";
import {isSql} from "@/components/Studio/conf";
import {useState} from "react";
import CodeShow from "@/components/Common/CodeShow"; import CodeShow from "@/components/Common/CodeShow";
import {getConsoleInfo} from "@/pages/SettingCenter/ProcessList/service";
const {Title, Paragraph, Text, Link} = Typography; import {RedoOutlined} from "@ant-design/icons";
import {Button} from "antd";
const StudioMsg = (props: any) => { const StudioMsg = (props: any) => {
const {current} = props; const {current} = props;
const [sqlModalVisit, setSqlModalVisit] = useState(false); const [consoleInfo, setConsoleInfo] = useState<string>("");
const [errorModalVisit, setErrorModalVisit] = useState(false);
const handleOpenSqlModal = () => {
setSqlModalVisit(true);
};
const handleOpenErrorModal = () => {
setErrorModalVisit(true);
};
const handleCancel = () => {
setSqlModalVisit(false);
setErrorModalVisit(false);
};
const renderCommonSqlContent = () => {
return (<>
<Paragraph>
<blockquote><Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime}
<Divider type="vertical"/>
{!(current.console.result.success) ? <><Badge status="error"/><Text type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical"/>
</blockquote>
{current.console.result.statement && (<pre style={{height: '100px'}}>{current.console.result.statement}</pre>)}
{current.console.result.error && (<pre style={{height: '100px'}}>{current.console.result.error}</pre>)}
</Paragraph>
</>)
};
const renderFlinkSqlContent = () => { useEffect(() => {
return (<> refreshConsoleInfo();
<Paragraph> }, []);
<blockquote><Link href={`http://${current.console.result.jobConfig?.address}`} target="_blank">
[{current.console.result.jobConfig?.session}:{current.console.result.jobConfig?.address}]
</Link> <Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime}
<Divider type="vertical"/>
{!(current.console.result.status === 'SUCCESS') ? <><Badge status="error"/><Text
type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical"/>
{current.console.result.jobConfig?.jobName && <Text code>{current.console.result.jobConfig?.jobName}</Text>}
{current.console.result.jobId &&
(<>
<Divider type="vertical"/>
<Tag color="blue" key={current.console.result.jobId}>
<FireOutlined/> {current.console.result.jobId}
</Tag>
</>)}
<Button
type="text"
icon={<ZoomInOutlined/>}
onClick={handleOpenSqlModal}
>
SQL
</Button>
{current.console.result.error ?
<Button
type="text"
icon={<ZoomInOutlined/>}
onClick={handleOpenErrorModal}
>
Error
</Button> : undefined
}
</blockquote>
{current.console.result.statement && (<pre style={{height: '100px'}}>{current.console.result.statement}</pre>)}
{current.console.result.error && (<pre style={{height: '100px'}}>{current.console.result.error}</pre>)}
</Paragraph>
</>)
};
const refreshConsoleInfo = () => {
const res = getConsoleInfo();
res.then((result) => {
result.datas && setConsoleInfo(result.datas);
});
}
return ( return (
<> <><Button
<Typography> icon={<RedoOutlined/>}
{current?.task && current.console.result.startTime ? (isSql(current.task.dialect) ? renderCommonSqlContent() : onClick={refreshConsoleInfo}
renderFlinkSqlContent()) : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/> ></Button>
} <CodeShow code={consoleInfo} language='java'
</Typography> height='500px' theme="vs-dark"/>
<Modal
width={'100%'}
visible={sqlModalVisit}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{current.console.result.statement &&
(<CodeShow height={"80vh"} language={"sql"} code={current.console.result.statement} theme={"vs-dark"}/>)}
</Modal>
<Modal
width={'100%'}
visible={errorModalVisit}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{current.console.result.error &&
(<CodeShow height={"80vh"} language={"java"} code={current.console.result.error} theme={"vs-dark"}/>)}
</Modal>
</> </>
); );
}; };
......
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {Badge, Button, Divider, Empty, Modal, Tag, Typography} from "antd";
import {StateType} from "@/pages/DataStudio/model";
import {connect} from "umi";
import {FireOutlined, ZoomInOutlined} from '@ant-design/icons';
import {isSql} from "@/components/Studio/conf";
import {useState} from "react";
import CodeShow from "@/components/Common/CodeShow";
const {Title, Paragraph, Text, Link} = Typography;
const StudioMsg = (props: any) => {
const {current} = props;
const [sqlModalVisit, setSqlModalVisit] = useState(false);
const [errorModalVisit, setErrorModalVisit] = useState(false);
const handleOpenSqlModal = () => {
setSqlModalVisit(true);
};
const handleOpenErrorModal = () => {
setErrorModalVisit(true);
};
const handleCancel = () => {
setSqlModalVisit(false);
setErrorModalVisit(false);
};
const renderCommonSqlContent = () => {
return (<>
<Paragraph>
<blockquote><Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime}
<Divider type="vertical"/>
{!(current.console.result.success) ? <><Badge status="error"/><Text type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical"/>
</blockquote>
{current.console.result.statement && (<pre style={{height: '100px'}}>{current.console.result.statement}</pre>)}
{current.console.result.error && (<pre style={{height: '100px'}}>{current.console.result.error}</pre>)}
</Paragraph>
</>)
};
const renderFlinkSqlContent = () => {
return (<>
<Paragraph>
<blockquote><Link href={`http://${current.console.result.jobConfig?.address}`} target="_blank">
[{current.console.result.jobConfig?.session}:{current.console.result.jobConfig?.address}]
</Link> <Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime}
<Divider type="vertical"/>
{!(current.console.result.status === 'SUCCESS') ? <><Badge status="error"/><Text
type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical"/>
{current.console.result.jobConfig?.jobName && <Text code>{current.console.result.jobConfig?.jobName}</Text>}
{current.console.result.jobId &&
(<>
<Divider type="vertical"/>
<Tag color="blue" key={current.console.result.jobId}>
<FireOutlined/> {current.console.result.jobId}
</Tag>
</>)}
<Button
type="text"
icon={<ZoomInOutlined/>}
onClick={handleOpenSqlModal}
>
SQL
</Button>
{current.console.result.error ?
<Button
type="text"
icon={<ZoomInOutlined/>}
onClick={handleOpenErrorModal}
>
Error
</Button> : undefined
}
</blockquote>
{current.console.result.statement && (<pre style={{height: '100px'}}>{current.console.result.statement}</pre>)}
{current.console.result.error && (<pre style={{height: '100px'}}>{current.console.result.error}</pre>)}
</Paragraph>
</>)
};
return (
<>
<Typography>
{current?.task && current.console.result.startTime ? (isSql(current.task.dialect) ? renderCommonSqlContent() :
renderFlinkSqlContent()) : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
}
</Typography>
<Modal
width={'100%'}
visible={sqlModalVisit}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{current.console.result.statement &&
(<CodeShow height={"80vh"} language={"sql"} code={current.console.result.statement} theme={"vs-dark"}/>)}
</Modal>
<Modal
width={'100%'}
visible={errorModalVisit}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{current.console.result.error &&
(<CodeShow height={"80vh"} language={"java"} code={current.console.result.error} theme={"vs-dark"}/>)}
</Modal>
</>
);
};
export default connect(({Studio}: { Studio: StateType }) => ({
current: Studio.current,
}))(StudioMsg);
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {getData} from "@/components/Common/crud";
export function getConsoleInfo() {
return getData("api/process/getConsoleByUserId");
}
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