diff --git a/packages/api/src/controllers/runners.js b/packages/api/src/controllers/runners.js
index 1c3e618f1..977fd85fb 100644
--- a/packages/api/src/controllers/runners.js
+++ b/packages/api/src/controllers/runners.js
@@ -1,6 +1,6 @@
const _ = require('lodash');
const path = require('path');
-const fs = require('fs');
+const fs = require('fs-extra');
const uuidv1 = require('uuid/v1');
const socket = require('../utility/socket');
const { fork } = require('child_process');
@@ -83,11 +83,26 @@ module.exports = {
cancel_meta: 'post',
async cancel({ runid }) {
- const session = this.opened.find((x) => x.runid == runid);
- if (!session) {
+ const runner = this.opened.find((x) => x.runid == runid);
+ if (!runner) {
throw new Error('Invalid runner');
}
- session.subprocess.kill();
+ runner.subprocess.kill();
return { state: 'ok' };
},
+
+ files_meta: 'get',
+ async files({ runid }) {
+ const directory = path.join(rundir(), runid);
+ const files = await fs.readdir(directory);
+ const res = [];
+ for (const file of files) {
+ const stat = await fs.stat(path.join(directory, file));
+ res.push({
+ name: file,
+ size: stat.size,
+ });
+ }
+ return res;
+ },
};
diff --git a/packages/api/src/main.js b/packages/api/src/main.js
index 9c91c33e8..838d0883e 100644
--- a/packages/api/src/main.js
+++ b/packages/api/src/main.js
@@ -19,6 +19,8 @@ const runners = require('./controllers/runners');
const jsldata = require('./controllers/jsldata');
const config = require('./controllers/config');
+const { rundir } = require('./utility/directories');
+
function start(argument = null) {
// console.log('process.argv', process.argv);
@@ -43,6 +45,8 @@ function start(argument = null) {
app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
}
+ app.use('/runners/data', express.static(rundir()));
+
if (fs.existsSync('/home/dbgate-docker/build')) {
// server static files inside docker container
app.use(express.static('/home/dbgate-docker/build'));
@@ -54,7 +58,7 @@ function start(argument = null) {
if (argument == '--dynport') {
childProcessChecker();
-
+
findFreePort(53911, function (err, port) {
server.listen(port, () => {
console.log(`DbGate API listening on port ${port}`);
diff --git a/packages/web/src/modals/ImportExportModal.js b/packages/web/src/modals/ImportExportModal.js
index 042e1dc94..ba7fe4506 100644
--- a/packages/web/src/modals/ImportExportModal.js
+++ b/packages/web/src/modals/ImportExportModal.js
@@ -42,7 +42,7 @@ export default function ImportExportModal({ modalState, initialValues }) {
-
+
diff --git a/packages/web/src/query/MessagesView.js b/packages/web/src/query/MessagesView.js
index fb5358e4a..d7e02511f 100644
--- a/packages/web/src/query/MessagesView.js
+++ b/packages/web/src/query/MessagesView.js
@@ -51,7 +51,7 @@ function formatDuration(duration) {
return `${Math.round(duration / 1000)} s`;
}
-function MessagesView({ items, onMessageClick }) {
+function MessagesView({ items, onMessageClick, showProcedure = false, showLine = false }) {
const handleClick = (row) => {
if (onMessageClick) onMessageClick(row);
};
@@ -76,8 +76,8 @@ function MessagesView({ items, onMessageClick }) {
Time
Delta
Duration
- Procedure
- Line
+ {showProcedure && Procedure}
+ {showLine && Line}
{items.map((row, index) => (
// @ts-ignore
@@ -91,8 +91,8 @@ function MessagesView({ items, onMessageClick }) {
? formatDuration(new Date(row.time).getTime() - new Date(items[index - 1].time).getTime())
: 'n/a'}
- {row.procedure}
- {row.line}
+ {showProcedure && {row.procedure}}
+ {showLine && {row.line}}
))}
@@ -100,4 +100,4 @@ function MessagesView({ items, onMessageClick }) {
);
}
-export default React.memo(MessagesView);
\ No newline at end of file
+export default React.memo(MessagesView);
diff --git a/packages/web/src/query/RunnerOuputFiles.js b/packages/web/src/query/RunnerOuputFiles.js
new file mode 100644
index 000000000..ed1cd9233
--- /dev/null
+++ b/packages/web/src/query/RunnerOuputFiles.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import useSocket from '../utility/SocketProvider';
+import axios from '../utility/axios';
+import styled from 'styled-components';
+import TableControl, { TableColumn } from '../utility/TableControl';
+import formatFileSize from '../utility/formatFileSize';
+import resolveApi from '../utility/resolveApi';
+
+export default function RunnerOutputFiles({ runnerId, executeNumber }) {
+ const socket = useSocket();
+ const [files, setFiles] = React.useState([]);
+
+ const handleRunnerDone = React.useCallback(async () => {
+ const resp = await axios.get(`runners/files?runid=${runnerId}`);
+ setFiles(resp.data);
+ }, [runnerId]);
+
+ React.useEffect(() => {
+ if (runnerId && socket) {
+ socket.on(`runner-done-${runnerId}`, handleRunnerDone);
+ return () => {
+ socket.off(`runner-done-${runnerId}`, handleRunnerDone);
+ };
+ }
+ }, [runnerId, socket]);
+
+ React.useEffect(() => {
+ setFiles([]);
+ }, [executeNumber]);
+
+ return (
+
+
+ formatFileSize(row.size)} />
+ (
+
+ download
+
+ )}
+ />
+
+ );
+}
diff --git a/packages/web/src/query/RunnerOutputPane.js b/packages/web/src/query/RunnerOutputPane.js
new file mode 100644
index 000000000..01e49bf9f
--- /dev/null
+++ b/packages/web/src/query/RunnerOutputPane.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import { HorizontalSplitter } from '../widgets/Splitter';
+import SocketMessagesView from './SocketMessagesView';
+import { WidgetTitle } from '../widgets/WidgetStyles';
+import RunnerOutputFiles from './RunnerOuputFiles';
+import styled from 'styled-components';
+
+const Container = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+`;
+
+export default function RunnerOutputPane({ runnerId, executeNumber }) {
+ return (
+
+
+ Messages
+
+
+
+ Output files
+
+
+
+ );
+}
diff --git a/packages/web/src/query/SocketMessagesView.js b/packages/web/src/query/SocketMessagesView.js
index 616368b0f..ed0d55171 100644
--- a/packages/web/src/query/SocketMessagesView.js
+++ b/packages/web/src/query/SocketMessagesView.js
@@ -3,7 +3,13 @@ import React from 'react';
import MessagesView from './MessagesView';
import useSocket from '../utility/SocketProvider';
-export default function SocketMessagesView({ eventName, onMessageClick = undefined, executeNumber }) {
+export default function SocketMessagesView({
+ eventName,
+ onMessageClick = undefined,
+ executeNumber,
+ showProcedure = false,
+ showLine = false,
+}) {
const [displayedMessages, setDisplayedMessages] = React.useState([]);
const cachedMessagesRef = React.useRef([]);
const socket = useSocket();
@@ -35,5 +41,12 @@ export default function SocketMessagesView({ eventName, onMessageClick = undefin
}
}, [eventName, socket]);
- return ;
+ return (
+
+ );
}
diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js
index 7cede062d..c2a72d085 100644
--- a/packages/web/src/tabs/QueryTab.js
+++ b/packages/web/src/tabs/QueryTab.js
@@ -209,6 +209,8 @@ export default function QueryTab({
eventName={sessionId ? `session-info-${sessionId}` : null}
onMessageClick={handleMesageClick}
executeNumber={executeNumber}
+ showProcedure
+ showLine
/>
diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js
index fd729462c..72e6de3a9 100644
--- a/packages/web/src/tabs/ShellTab.js
+++ b/packages/web/src/tabs/ShellTab.js
@@ -11,7 +11,7 @@ import QueryToolbar from '../query/QueryToolbar';
import SocketMessagesView from '../query/SocketMessagesView';
import { TabPage } from '../widgets/TabControl';
import ResultTabs from '../sqleditor/ResultTabs';
-import { VerticalSplitter } from '../widgets/Splitter';
+import { VerticalSplitter, HorizontalSplitter } from '../widgets/Splitter';
import keycodes from '../utility/keycodes';
import { changeTab } from '../utility/common';
import useSocket from '../utility/SocketProvider';
@@ -20,6 +20,7 @@ import useModalState from '../modals/useModalState';
import sqlFormatter from 'sql-formatter';
import JavaScriptEditor from '../sqleditor/JavaScriptEditor';
import ShellToolbar from '../query/ShellToolbar';
+import RunnerOutputPane from '../query/RunnerOutputPane';
export default function ShellTab({
tabid,
@@ -128,17 +129,13 @@ export default function ShellTab({
onKeyDown={handleKeyDown}
editorRef={editorRef}
/>
-
+
{toolbarPortalRef &&
toolbarPortalRef.current &&
tabVisible &&
ReactDOM.createPortal(
- ,
+ ,
toolbarPortalRef.current
)}
>
diff --git a/packages/web/src/utility/formatFileSize.js b/packages/web/src/utility/formatFileSize.js
new file mode 100644
index 000000000..059569559
--- /dev/null
+++ b/packages/web/src/utility/formatFileSize.js
@@ -0,0 +1,6 @@
+export default function formatFileSize(size) {
+ if (size > 1000000000) return `${Math.round(size / 10000000000) * 10} GB`;
+ if (size > 1000000) return `${Math.round(size / 10000000) * 10} MB`;
+ if (size > 1000) return `${Math.round(size / 10000) * 10} KB`;
+ return `${size} bytes`;
+}