diff --git a/packages/api/src/controllers/runners.js b/packages/api/src/controllers/runners.js index c6c28cd56..cdaaa36b1 100644 --- a/packages/api/src/controllers/runners.js +++ b/packages/api/src/controllers/runners.js @@ -107,6 +107,10 @@ module.exports = { } }, + handle_progress(runid, { progressName, status }) { + socket.emit(`runner-progress-${runid}`, { progressName, status }); + }, + rejectRequest(runid, error) { if (this.requests[runid]) { const { reject } = this.requests[runid]; diff --git a/packages/api/src/shell/copyStream.js b/packages/api/src/shell/copyStream.js index be0c8ea31..c73476d62 100644 --- a/packages/api/src/shell/copyStream.js +++ b/packages/api/src/shell/copyStream.js @@ -10,7 +10,15 @@ const streamPipeline = require('../utility/streamPipeline'); * @returns {Promise} */ async function copyStream(input, output, options) { - const { columns } = options || {}; + const { columns, progressName } = options || {}; + + if (progressName) { + process.send({ + msgtype: 'progress', + progressName, + status: 'running', + }); + } const transforms = []; if (columns) { @@ -22,6 +30,14 @@ async function copyStream(input, output, options) { try { await streamPipeline(input, transforms, output); + + if (progressName) { + process.send({ + msgtype: 'progress', + progressName, + status: 'done', + }); + } } catch (err) { process.send({ msgtype: 'copyStreamError', @@ -30,6 +46,15 @@ async function copyStream(input, output, options) { ...err, }, }); + + if (progressName) { + process.send({ + msgtype: 'progress', + progressName, + status: 'error', + }); + } + throw err; } } diff --git a/packages/tools/src/ScriptWriter.ts b/packages/tools/src/ScriptWriter.ts index 7c4f93b09..23afee0f4 100644 --- a/packages/tools/src/ScriptWriter.ts +++ b/packages/tools/src/ScriptWriter.ts @@ -41,12 +41,13 @@ export class ScriptWriter { this.packageNames.push(packageName); } - copyStream(sourceVar, targetVar, colmapVar = null) { - if (colmapVar) { - this._put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar}, {columns: ${colmapVar}});`); - } else { - this._put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`); - } + copyStream(sourceVar, targetVar, colmapVar = null, progressName?: string) { + let opts = '{'; + if (colmapVar) opts += `columns: ${colmapVar}, `; + if (progressName) opts += `progressName: "${progressName}", `; + opts += '}'; + + this._put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar}, ${opts});`); } dumpDatabase(options) { @@ -117,12 +118,13 @@ export class ScriptWriterJson { }); } - copyStream(sourceVar, targetVar, colmapVar = null) { + copyStream(sourceVar, targetVar, colmapVar = null, progressName?: string) { this.commands.push({ type: 'copyStream', sourceVar, targetVar, colmapVar, + progressName, }); } @@ -183,7 +185,7 @@ export function jsonScriptToJavascript(json) { script.assignValue(cmd.variableName, cmd.jsonValue); break; case 'copyStream': - script.copyStream(cmd.sourceVar, cmd.targetVar, cmd.colmapVar); + script.copyStream(cmd.sourceVar, cmd.targetVar, cmd.colmapVar, cmd.progressName); break; case 'endLine': script.endLine(); diff --git a/packages/web/src/elements/TableControl.svelte b/packages/web/src/elements/TableControl.svelte index 931f8f8ec..e8eb1051b 100644 --- a/packages/web/src/elements/TableControl.svelte +++ b/packages/web/src/elements/TableControl.svelte @@ -20,7 +20,7 @@ import { createEventDispatcher } from 'svelte'; import FontIcon from '../icons/FontIcon.svelte'; - export let columns: TableControlColumn[]; + export let columns: (TableControlColumn | false)[]; export let rows; export let focusOnCreate = false; export let selectable = false; diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index c5ed2f18a..df808a488 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -149,6 +149,7 @@ 'icon download': 'mdi mdi-download', 'icon text': 'mdi mdi-text', 'icon ai': 'mdi mdi-head-lightbulb', + 'icon wait': 'mdi mdi-timer-sand', 'icon run': 'mdi mdi-play', 'icon chevron-down': 'mdi mdi-chevron-down', diff --git a/packages/web/src/impexp/ImportExportConfigurator.svelte b/packages/web/src/impexp/ImportExportConfigurator.svelte index efb286ded..8889f763d 100644 --- a/packages/web/src/impexp/ImportExportConfigurator.svelte +++ b/packages/web/src/impexp/ImportExportConfigurator.svelte @@ -104,6 +104,7 @@ $: sourceList = $values.sourceList; let targetEditKey = 0; + export let progressHolder = null; const previewSource = writable(null); @@ -231,11 +232,16 @@ header: 'Target', slot: 1, }, - { + supportsPreview && { fieldName: 'preview', header: 'Preview', slot: 0, }, + !!progressHolder && { + fieldName: 'status', + header: 'Status', + slot: 3, + }, { fieldName: 'columns', header: 'Columns', @@ -296,6 +302,17 @@ >{columnCount > 0 ? `(${columnCount} columns)` : '(copy from source)'} + + {#if progressHolder[row]?.status == 'running'} + Running + {:else if progressHolder[row]?.status == 'error'} + Error + {:else if progressHolder[row]?.status == 'done'} + Done + {:else} + Queued + {/if} + {/key} diff --git a/packages/web/src/impexp/createImpExpScript.ts b/packages/web/src/impexp/createImpExpScript.ts index 0fa3c076c..c9961fb7b 100644 --- a/packages/web/src/impexp/createImpExpScript.ts +++ b/packages/web/src/impexp/createImpExpScript.ts @@ -233,7 +233,7 @@ export default async function createImpExpScript(extensions, values, forceScript script.assignValue(colmapVar, colmap); } - script.copyStream(sourceVar, targetVar, colmapVar); + script.copyStream(sourceVar, targetVar, colmapVar, sourceName); script.endLine(); } return script.getScript(values.schedule); diff --git a/packages/web/src/tabs/ImportExportTab.svelte b/packages/web/src/tabs/ImportExportTab.svelte index 37e4321cc..ab027d607 100644 --- a/packages/web/src/tabs/ImportExportTab.svelte +++ b/packages/web/src/tabs/ImportExportTab.svelte @@ -65,6 +65,7 @@ export let savedFile; export let savedFilePath; + let progressHolder = null; const refreshArchiveFolderRef = createRef(null); const formValues = writable({}); @@ -179,6 +180,7 @@ const handleExecute = async e => { if (busy) return; + progressHolder = {}; const values = $formValues as any; busy = true; const script = await createImpExpScript($extensions, values); @@ -228,6 +230,29 @@ title: `${getSourceTargetTitle('source', values)}->${getSourceTargetTitle('target', values)}(${values.sourceList?.length || 0})`, })); } + + const handleProgress = progress => { + progressHolder = { + ...progressHolder, + [progress.progressName]: { + ...progressHolder[progress.progressName], + ...progress, + }, + }; + }; + + $: progressEffect = useEffect(() => { + if (runnerId) { + const eventName = `runner-progress-${runnerId}`; + apiOn(eventName, handleProgress); + return () => { + apiOff(eventName, handleProgress); + }; + } + return () => {}; + }); + + $progressEffect; @@ -237,6 +262,7 @@