diff --git a/packages/api/package.json b/packages/api/package.json index d49892339..5edd9493f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -38,6 +38,7 @@ "lodash": "^4.17.15", "ncp": "^2.0.0", "nedb-promises": "^4.0.1", + "node-cron": "^2.0.3", "tar": "^6.0.5", "uuid": "^3.4.0" }, diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index c69e36642..35399c7d2 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -2,6 +2,7 @@ const fs = require('fs-extra'); const path = require('path'); const { filesdir } = require('../utility/directories'); const socket = require('../utility/socket'); +const scheduler = require('./scheduler'); function serialize(format, data) { if (format == 'text') return data; @@ -44,5 +45,8 @@ module.exports = { } await fs.writeFile(path.join(dir, file), serialize(format, data)); socket.emitChanged(`files-changed-${folder}`); + if (folder == 'shell') { + scheduler.reload(); + } }, }; diff --git a/packages/api/src/controllers/scheduler.js b/packages/api/src/controllers/scheduler.js new file mode 100644 index 000000000..3a68e9922 --- /dev/null +++ b/packages/api/src/controllers/scheduler.js @@ -0,0 +1,41 @@ +const { filesdir } = require('../utility/directories'); +const fs = require('fs-extra'); +const path = require('path'); +const cron = require('node-cron'); +const runners = require('./runners'); + +const scheduleRegex = /\s*\/\/\s*@schedule\s+([^\n]+)\n/; + +module.exports = { + tasks: [], + + async unload() { + this.tasks.forEach((x) => x.destroy()); + this.tasks = []; + }, + + async processFile(file) { + const text = await fs.readFile(file, { encoding: 'utf-8' }); + const match = text.match(scheduleRegex); + if (!match) return; + const pattern = match[1]; + if (!cron.validate(pattern)) return; + console.log(`Schedule script ${file} with pattern ${pattern}`); + const task = cron.schedule(pattern, () => runners.start({ script: text })); + this.tasks.push(task); + }, + + async reload() { + const shellDir = path.join(filesdir(), 'shell'); + await this.unload(); + if (!(await fs.exists(shellDir))) return; + const files = await fs.readdir(shellDir); + for (const file of files) { + await this.processFile(path.join(shellDir, file)); + } + }, + + async _init() { + this.reload(); + }, +}; diff --git a/packages/api/src/main.js b/packages/api/src/main.js index cbc0eaed8..16cacd4f0 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -24,6 +24,7 @@ const archive = require('./controllers/archive'); const uploads = require('./controllers/uploads'); const plugins = require('./controllers/plugins'); const files = require('./controllers/files'); +const scheduler = require('./controllers/scheduler'); const { rundir } = require('./utility/directories'); @@ -69,6 +70,7 @@ function start(argument = null) { useController(app, '/uploads', uploads); useController(app, '/plugins', plugins); useController(app, '/files', files); + useController(app, '/scheduler', scheduler); if (process.env.PAGES_DIRECTORY) { app.use('/pages', express.static(process.env.PAGES_DIRECTORY)); diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index f5bcd52d7..79ab4809a 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -32,9 +32,10 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib const extensions = useExtensions(); const { editorData, setEditorData, isLoading } = useEditorData({ tabid, - loadFromArgs: initialArgs && initialArgs.sqlTemplate - ? () => applySqlTemplate(initialArgs.sqlTemplate, extensions, { conid, database, ...other }) - : null, + loadFromArgs: + initialArgs && initialArgs.sqlTemplate + ? () => applySqlTemplate(initialArgs.sqlTemplate, extensions, { conid, database, ...other }) + : null, }); const editorRef = React.useRef(null); @@ -113,12 +114,13 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib editorRef.current.editor.clearSelection(); }; - if (isLoading) + if (isLoading) { return (
); + } return ( <> diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js index 4935fbf11..32d742791 100644 --- a/packages/web/src/tabs/ShellTab.js +++ b/packages/web/src/tabs/ShellTab.js @@ -15,6 +15,7 @@ import ImportExportModal from '../modals/ImportExportModal'; import useEditorData from '../utility/useEditorData'; import SaveTabModal from '../modals/SaveTabModal'; import useModalState from '../modals/useModalState'; +import LoadingInfo from '../widgets/LoadingInfo'; const configRegex = /\s*\/\/\s*@ImportExportConfigurator\s*\n\s*\/\/\s*(\{[^\n]+\})\n/; const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g; @@ -22,7 +23,7 @@ const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g; export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other }) { const [busy, setBusy] = React.useState(false); const showModal = useShowModal(); - const { editorData, setEditorData } = useEditorData({ tabid }); + const { editorData, setEditorData, isLoading } = useEditorData({ tabid }); const saveFileModalState = useModalState(); const setOpenedTabs = useSetOpenedTabs(); @@ -89,6 +90,14 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other } }; + if (isLoading) { + return ( +
+ +
+ ); + } + return ( <> diff --git a/yarn.lock b/yarn.lock index 53fd2c5c4..6a64ad2f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7882,6 +7882,14 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" +node-cron@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-2.0.3.tgz#b9649784d0d6c00758410eef22fa54a10e3f602d" + integrity sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg== + dependencies: + opencollective-postinstall "^2.0.0" + tz-offset "0.0.1" + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -8208,6 +8216,11 @@ open@^6.4.0: dependencies: is-wsl "^1.1.0" +opencollective-postinstall@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -11627,6 +11640,11 @@ typescript@^3.7.4, typescript@^3.7.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== +tz-offset@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tz-offset/-/tz-offset-0.0.1.tgz#fef920257024d3583ed9072a767721a18bdb8a76" + integrity sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ== + ua-parser-js@^0.7.18: version "0.7.22" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"