diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 5d4d87bb7..07efaf18e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -28,7 +28,7 @@ To help us understand the issue, please fill-in as much of the following informa ### Please tell us about your environment: - [ ] Node-RED version: -- [ ] node.js version: +- [ ] Node.js version: - [ ] npm version: - [ ] Platform/OS: - [ ] Browser: diff --git a/.github/ISSUE_TEMPLATE/--bug_report.md b/.github/ISSUE_TEMPLATE/--bug_report.md index ff13e2ace..63923455e 100644 --- a/.github/ISSUE_TEMPLATE/--bug_report.md +++ b/.github/ISSUE_TEMPLATE/--bug_report.md @@ -33,7 +33,7 @@ To help us understand the issue, please fill-in as much of the following informa ### Please tell us about your environment: - [ ] Node-RED version: -- [ ] node.js version: +- [ ] Node.js version: - [ ] npm version: - [ ] Platform/OS: - [ ] Browser: diff --git a/.travis.yml b/.travis.yml index 14236730a..a101eecb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,5 +9,3 @@ matrix: before_script: - npm install -g istanbul coveralls - node_js: "8" - allow_failures: - - node_js: "12" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb84978a..793b5c565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -725,7 +725,7 @@ Nodes - Initial support of sequence rules for SWITCH node (#1545) - initial support of SORT node (#1500) - Inject node - let once delay be editable (#1541) - - Introduce `nodeMaxMessageBufferLength` setting for msg sequence nodes + - Introduce `nodeMessageBufferMaxLength` setting for msg sequence nodes - Let CSV correct parts if we remove header row. - let default apply if msg.delay not set in override mode. (#1397) - let trigger node be reset by boolean message (#1554) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33a2f582f..f0f4096c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ relevant nodes, press Ctrl-E and copy the flow data from the Export dialog. At a minimum, please include: - Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly. - - Version of node.js - what does `node -v` say? + - Version of Node.js - what does `node -v` say? ## Feature requests diff --git a/Gruntfile.js b/Gruntfile.js index 8b7b85a25..66ebf4f38 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -496,7 +496,9 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-chmod'); grunt.loadNpmTasks('grunt-jsonlint'); grunt.loadNpmTasks('grunt-mocha-istanbul'); - grunt.loadNpmTasks('grunt-webdriver'); + if (fs.existsSync(path.join("node_modules", "grunt-webdriver"))) { + grunt.loadNpmTasks('grunt-webdriver'); + } grunt.loadNpmTasks('grunt-jsdoc'); grunt.loadNpmTasks('grunt-jsdoc-to-markdown'); grunt.loadNpmTasks('grunt-npm-command'); @@ -555,8 +557,8 @@ module.exports = function(grunt) { }); grunt.registerTask('verifyUiTestDependencies', function() { - if (!fs.existsSync(path.join("node_modules", "chromedriver"))) { - grunt.fail.fatal('You need to run "npm install chromedriver@2" before running UI test.'); + if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) { + grunt.fail.fatal('You need to install the UI test dependencies first.\nUse the script in "scripts/install-ui-test-dependencies.sh"'); return false; } }); @@ -579,9 +581,15 @@ module.exports = function(grunt) { 'Runs code style check on editor code', ['jshint:editor']); - grunt.registerTask('test-ui', - 'Builds editor content then runs unit tests on editor ui', - ['verifyUiTestDependencies','build','jshint:editor','webdriver:all']); + if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) { + grunt.registerTask('test-ui', + 'Builds editor content then runs unit tests on editor ui', + ['verifyUiTestDependencies']); + } else { + grunt.registerTask('test-ui', + 'Builds editor content then runs unit tests on editor ui', + ['verifyUiTestDependencies','build','jshint:editor','webdriver:all']); + } grunt.registerTask('test-nodes', 'Runs unit tests on core nodes', diff --git a/package.json b/package.json index b68208e78..e0997ce16 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,9 @@ "mqtt": "2.18.8", "multer": "1.4.1", "mustache": "3.0.1", - "node-red-node-email": "^1.6.2", - "node-red-node-feedparser": "^0.1.14", "node-red-node-rbe": "^0.2.4", "node-red-node-sentiment": "^0.1.3", "node-red-node-tail": "^0.0.2", - "node-red-node-twitter": "^1.1.5", "nopt": "4.0.1", "oauth2orize": "1.11.0", "on-headers": "1.0.2", @@ -98,7 +95,6 @@ "grunt-npm-command": "~0.1.2", "grunt-sass": "~2.0.0", "grunt-simple-mocha": "~0.4.1", - "grunt-webdriver": "^2.0.3", "http-proxy": "^1.16.2", "istanbul": "0.4.5", "minami": "1.2.3", @@ -108,11 +104,7 @@ "sinon": "1.17.7", "stoppable": "^1.1.0", "supertest": "3.4.2", - "wdio-chromedriver-service": "^0.1.5", - "wdio-mocha-framework": "^0.6.4", - "wdio-spec-reporter": "^0.1.5", - "webdriverio": "^4.14.1", - "node-red-node-test-helper": "^0.2.2", + "node-red-node-test-helper": "^0.2.3", "jsdoc-nr-template": "node-red/jsdoc-nr-template" }, "engines": { diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/context.js b/packages/node_modules/@node-red/editor-api/lib/admin/context.js index 6a2efd82d..54bfd9f85 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/context.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/context.js @@ -30,7 +30,8 @@ module.exports = { scope: req.params.scope, id: req.params.id, key: req.params[0], - store: req.query['store'] + store: req.query['store'], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.context.getValue(opts).then(function(result) { res.json(result); @@ -45,7 +46,8 @@ module.exports = { scope: req.params.scope, id: req.params.id, key: req.params[0], - store: req.query['store'] + store: req.query['store'], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.context.delete(opts).then(function(result) { res.status(204).end(); diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flow.js b/packages/node_modules/@node-red/editor-api/lib/admin/flow.js index 5ba5d7a04..98ae997ff 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flow.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flow.js @@ -24,7 +24,8 @@ module.exports = { get: function(req,res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.getFlow(opts).then(function(result) { return res.json(result); @@ -35,7 +36,8 @@ module.exports = { post: function(req,res) { var opts = { user: req.user, - flow: req.body + flow: req.body, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.addFlow(opts).then(function(id) { return res.json({id:id}); @@ -47,7 +49,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - flow: req.body + flow: req.body, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.updateFlow(opts).then(function(id) { return res.json({id:id}); @@ -58,7 +61,8 @@ module.exports = { delete: function(req,res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.deleteFlow(opts).then(function() { res.status(204).end(); diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js index ccad8718f..11b30e446 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js @@ -27,7 +27,8 @@ module.exports = { return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"}); } var opts = { - user: req.user + user: req.user, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.getFlows(opts).then(function(result) { if (version === "v1") { @@ -46,7 +47,8 @@ module.exports = { } var opts = { user: req.user, - deploymentType: req.get("Node-RED-Deployment-Type")||"full" + deploymentType: req.get("Node-RED-Deployment-Type")||"full", + req: apiUtils.getRequestLogObject(req) } if (opts.deploymentType !== 'reload') { diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index 32bf010c5..50d7b168f 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -48,13 +48,13 @@ module.exports = { // Nodes adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler); adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler); - adminApp.get(/\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler); - adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler); - adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler); - adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler); - adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler); - adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler); - adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler); + adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler); + adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler); + adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler); + adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler); + adminApp.delete(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler); + adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler); + adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler); // Context adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler); diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js index 59a137587..2787a5c36 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js @@ -24,7 +24,8 @@ module.exports = { }, getAll: function(req,res) { var opts = { - user: req.user + user: req.user, + req: apiUtils.getRequestLogObject(req) } if (req.get("accept") == "application/json") { runtimeAPI.nodes.getNodeList(opts).then(function(list) { @@ -42,7 +43,8 @@ module.exports = { var opts = { user: req.user, module: req.body.module, - version: req.body.version + version: req.body.version, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.addModule(opts).then(function(info) { res.json(info); @@ -54,7 +56,8 @@ module.exports = { delete: function(req,res) { var opts = { user: req.user, - module: req.params[0] + module: req.params[0], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.removeModule(opts).then(function() { res.status(204).end(); @@ -66,7 +69,8 @@ module.exports = { getSet: function(req,res) { var opts = { user: req.user, - id: req.params[0] + "/" + req.params[2] + id: req.params[0] + "/" + req.params[2], + req: apiUtils.getRequestLogObject(req) } if (req.get("accept") === "application/json") { runtimeAPI.nodes.getNodeInfo(opts).then(function(result) { @@ -87,7 +91,8 @@ module.exports = { getModule: function(req,res) { var opts = { user: req.user, - module: req.params[0] + module: req.params[0], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.getModuleInfo(opts).then(function(result) { res.send(result); @@ -106,7 +111,8 @@ module.exports = { var opts = { user: req.user, id: req.params[0] + "/" + req.params[2], - enabled: body.enabled + enabled: body.enabled, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.setNodeSetState(opts).then(function(result) { res.send(result); @@ -125,7 +131,8 @@ module.exports = { var opts = { user: req.user, module: req.params[0], - enabled: body.enabled + enabled: body.enabled, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.setModuleState(opts).then(function(result) { res.send(result); @@ -139,7 +146,8 @@ module.exports = { var opts = { user: req.user, module: req.params[0], - lang: req.query.lng + lang: req.query.lng, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) { res.json(result); @@ -152,7 +160,8 @@ module.exports = { getModuleCatalogs: function(req,res) { var opts = { user: req.user, - lang: req.query.lng + lang: req.query.lng, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) { res.json(result); @@ -164,7 +173,8 @@ module.exports = { getIcons: function(req,res) { var opts = { - user: req.user + user: req.user, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.getIconList(opts).then(function(list) { res.json(list); diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/projects.js b/packages/node_modules/@node-red/editor-api/lib/editor/projects.js index ff7fc5e85..0849c8ff8 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/projects.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/projects.js @@ -22,7 +22,8 @@ var needsPermission = require("../auth").needsPermission; function listProjects(req,res) { var opts = { - user: req.user + user: req.user, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.listProjects(opts).then(function(result) { res.json(result); @@ -33,7 +34,8 @@ function listProjects(req,res) { function getProject(req,res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getProject(opts).then(function(data) { if (data) { @@ -49,7 +51,8 @@ function getProjectStatus(req,res) { var opts = { user: req.user, id: req.params.id, - remote: req.query.remote + remote: req.query.remote, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getStatus(opts).then(function(data){ if (data) { @@ -64,7 +67,8 @@ function getProjectStatus(req,res) { function getProjectRemotes(req,res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getRemotes(opts).then(function(data) { res.json(data); @@ -98,7 +102,8 @@ module.exports = { app.post("/", needsPermission("projects.write"), function(req,res) { var opts = { user: req.user, - project: req.body + project: req.body, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.createProject(opts).then(function(result) { res.json(result); @@ -112,7 +117,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - project: req.body + project: req.body, + req: apiUtils.getRequestLogObject(req) } if (req.body.active) { @@ -150,7 +156,8 @@ module.exports = { app.delete("/:id", needsPermission("projects.write"), function(req,res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.deleteProject(opts).then(function() { res.status(204).end(); @@ -168,7 +175,8 @@ module.exports = { app.get("/:id/files", needsPermission("projects.read"), function(req,res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getFiles(opts).then(function(data) { res.json(data); @@ -185,7 +193,8 @@ module.exports = { user: req.user, id: req.params.id, path: req.params[0], - tree: req.params.treeish + tree: req.params.treeish, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getFile(opts).then(function(data) { res.json({content:data}); @@ -199,7 +208,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - path: req.params[0] + path: req.params[0], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.revertFile(opts).then(function() { @@ -214,7 +224,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - path: req.params[0] + path: req.params[0], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.stageFile(opts).then(function() { getProjectStatus(req,res); @@ -228,7 +239,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - path: req.body.files + path: req.body.files, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.stageFile(opts).then(function() { getProjectStatus(req,res); @@ -242,7 +254,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - message: req.body.message + message: req.body.message, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.commit(opts).then(function() { getProjectStatus(req,res); @@ -256,7 +269,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - path: req.params[0] + path: req.params[0], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.unstageFile(opts).then(function() { getProjectStatus(req,res); @@ -269,7 +283,8 @@ module.exports = { app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.unstageFile(opts).then(function() { getProjectStatus(req,res); @@ -284,7 +299,8 @@ module.exports = { user: req.user, id: req.params.id, path: req.params[0], - type: req.params.type + type: req.params.type, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getFileDiff(opts).then(function(data) { res.json({ @@ -301,7 +317,8 @@ module.exports = { user: req.user, id: req.params.id, limit: req.query.limit || 20, - before: req.query.before + before: req.query.before, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getCommits(opts).then(function(data) { res.json(data); @@ -315,7 +332,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - sha: req.params.sha + sha: req.params.sha, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getCommit(opts).then(function(data) { res.json({commit:data}); @@ -330,7 +348,8 @@ module.exports = { user: req.user, id: req.params.id, remote: req.params[0], - track: req.query.u + track: req.query.u, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.push(opts).then(function(data) { res.status(204).end(); @@ -346,7 +365,8 @@ module.exports = { id: req.params.id, remote: req.params[0], track: req.query.setUpstream, - allowUnrelatedHistories: req.query.allowUnrelatedHistories + allowUnrelatedHistories: req.query.allowUnrelatedHistories, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.pull(opts).then(function(data) { res.status(204).end(); @@ -359,7 +379,8 @@ module.exports = { app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) { var opts = { user: req.user, - id: req.params.id + id: req.params.id, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.abortMerge(opts).then(function() { res.status(204).end(); @@ -374,7 +395,8 @@ module.exports = { user: req.user, id: req.params.id, path: req.params[0], - resolution: req.body.resolutions + resolution: req.body.resolutions, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.resolveMerge(opts).then(function() { res.status(204).end(); @@ -388,7 +410,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - remote: false + remote: false, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getBranches(opts).then(function(data) { res.json(data); @@ -403,7 +426,8 @@ module.exports = { user: req.user, id: req.params.id, branch: req.params.branchName, - force: !!req.query.force + force: !!req.query.force, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.deleteBranch(opts).then(function(data) { res.status(204).end(); @@ -417,7 +441,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - remote: true + remote: true, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getBranches(opts).then(function(data) { res.json(data); @@ -431,7 +456,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - branch: req.params[0] + branch: req.params[0], + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.getBranchStatus(opts).then(function(data) { res.json(data); @@ -446,7 +472,8 @@ module.exports = { user: req.user, id: req.params.id, branch: req.body.name, - create: req.body.create + create: req.body.create, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.setBranch(opts).then(function(data) { res.json(data); @@ -463,7 +490,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - remote: req.body + remote: req.body, + req: apiUtils.getRequestLogObject(req) } if (/^https?:\/\/[^/]+@/i.test(req.body.url)) { res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"}); @@ -481,7 +509,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - remote: req.params.remoteName + remote: req.params.remoteName, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.removeRemote(opts).then(function(data) { getProjectRemotes(req,res); @@ -497,7 +526,8 @@ module.exports = { var opts = { user: req.user, id: req.params.id, - remote: remote + remote: remote, + req: apiUtils.getRequestLogObject(req) } runtimeAPI.projects.updateRemote(opts).then(function() { res.status(204).end(); diff --git a/packages/node_modules/@node-red/editor-api/lib/util.js b/packages/node_modules/@node-red/editor-api/lib/util.js index 1984bd5f1..0cef96bbb 100644 --- a/packages/node_modules/@node-red/editor-api/lib/util.js +++ b/packages/node_modules/@node-red/editor-api/lib/util.js @@ -47,5 +47,12 @@ module.exports = { code: err.code||"unexpected_error", message: err.message||err.toString() }); + }, + getRequestLogObject: function(req) { + return { + user: req.user, + path: req.path, + ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined + } } } diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 16b1ea97a..de38dad17 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -322,6 +322,31 @@ "description": "Description", "show": "Show", "hide": "Hide", + "locale": "Select UI Language", + "icon": "Icon", + "inputType": "Input type", + "previewUI": "Preview UI", + "previewOK": "Preview OK", + "types": { + "str": "string", + "num": "number", + "bool": "bool", + "json": "json", + "bin": "buffer", + "env": "env var", + "no-value": "no value" + }, + "menu": { + "input": "input", + "select": "select", + "checkbox": "checkbox", + "spinner": "spinner", + "hidden": "label only" + }, + "spinner": { + "min": "min", + "max": "max" + }, "errors": { "scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it", "invalidProperties": "Invalid properties:" @@ -940,9 +965,11 @@ }, "editor-tab": { "properties": "Properties", + "envProperties": "Environment Variables", "description": "Description", "appearance": "Appearance", - "env": "Environment Variables" + "preview": "UI Preview", + "defaultValue": "Default value" }, "languages" : { "de": "German", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 1cf864c63..e0953396a 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -80,7 +80,7 @@ "projects-new": "新規", "projects-open": "開く", "projects-settings": "設定", - "showNodeLabelDefault": "追加したノードのラベルを表示する" + "showNodeLabelDefault": "追加したノードのラベルを表示" } }, "actions": { @@ -322,6 +322,31 @@ "description": "詳細", "show": "表示", "hide": "非表示", + "locale": "UI言語の選択", + "icon": "記号", + "inputType": "入力形式", + "previewUI": "UI確認", + "previewOK": "確認OK", + "types": { + "str": "文字列", + "num": "数値", + "bool": "真偽", + "json": "JSON", + "bin": "バッファ", + "env": "環境変数", + "no-value": "値無し" + }, + "menu": { + "input": "入力", + "select": "選択", + "checkbox": "チェックボックス", + "spinner": "数値", + "hidden": "ラベルのみ" + }, + "spinner": { + "min": "最小", + "max": "最大" + }, "errors": { "scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします", "invalidProperties": "プロパティが不正です:" @@ -352,7 +377,8 @@ "pasteNode": "ノードを貼り付け", "undoChange": "変更操作を戻す", "searchBox": "ノードを検索", - "managePalette": "パレットの管理" + "managePalette": "パレットの管理", + "actionList": "動作一覧" }, "library": { "library": "ライブラリ", @@ -528,11 +554,13 @@ "none": "選択されていません", "refresh": "読み込みのため更新してください", "empty": "データが存在しません", - "node": "Node", - "flow": "Flow", - "global": "Global", + "node": "ノード", + "flow": "フロー", + "global": "グローバル", "deleteConfirm": "データを削除しても良いですか?", - "autoRefresh": "自動更新" + "autoRefresh": "自動更新", + "refrsh": "更新", + "delete": "削除" }, "palette": { "name": "パレットの管理", @@ -737,7 +765,16 @@ }, "jsonEditor": { "title": "JSONエディタ", - "format": "JSONフォーマット" + "format": "JSONフォーマット", + "rawMode": "JSONを編集", + "uiMode": "ビジュアルエディタ", + "insertAbove": "上に挿入", + "insertBelow": "下に挿入", + "addItem": "要素を追加", + "copyPath": "要素のパスをコピー", + "expandItems": "要素を展開", + "collapseItems": "要素を折り畳む", + "duplicate": "複製" }, "markdownEditor": { "title": "マークダウンエディタ", @@ -929,9 +966,9 @@ "properties": "プロパティ", "description": "説明", "appearance": "外観", - "env": "環境変数" + "env": "サブフロープロパティ" }, - "languages" : { + "languages": { "de": "ドイツ語", "en-US": "英語", "ja": "日本語", diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 930598dd5..db54701bb 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -14,7 +14,8 @@ * limitations under the License. **/ RED.history = (function() { - var undo_history = []; + var undoHistory = []; + var redoHistory = []; function undoEvent(ev) { var i; @@ -22,52 +23,81 @@ RED.history = (function() { var node; var subflow; var modifiedTabs = {}; + var inverseEv; if (ev) { if (ev.t == 'multi') { + inverseEv = { + t: 'multi', + events: [] + }; len = ev.events.length; for (i=len-1;i>=0;i--) { - undoEvent(ev.events[i]); + var r = undoEvent(ev.events[i]); + inverseEv.events.push(r); } } else if (ev.t == 'replace') { + inverseEv = { + t: 'replace', + config: RED.nodes.createCompleteNodeSet(), + changed: [], + rev: RED.nodes.version() + }; RED.nodes.clear(); var imported = RED.nodes.import(ev.config); imported[0].forEach(function(n) { if (ev.changed[n.id]) { n.changed = true; + inverseEv.changed[n.id] = true; } }) RED.nodes.version(ev.rev); } else if (ev.t == 'add') { + inverseEv = { + t: "delete", + }; if (ev.nodes) { + inverseEv.nodes = []; for (i=0;i ev.subflow.inputCount) { + inverseEv.subflow.inputs = ev.node.in.slice(ev.subflow.inputCount); ev.node.in.splice(ev.subflow.inputCount); } else if (ev.subflow.inputs.length > 0) { ev.node.in = ev.node.in.concat(ev.subflow.inputs); } } if (ev.subflow.hasOwnProperty('outputCount')) { + inverseEv.subflow.outputCount = ev.node.out.length; if (ev.node.out.length > ev.subflow.outputCount) { + inverseEv.subflow.outputs = ev.node.out.slice(ev.subflow.outputCount); ev.node.out.splice(ev.subflow.outputCount); } else if (ev.subflow.outputs.length > 0) { ev.node.out = ev.node.out.concat(ev.subflow.outputs); } } if (ev.subflow.hasOwnProperty('instances')) { + inverseEv.subflow.instances = []; ev.subflow.instances.forEach(function(n) { + inverseEv.subflow.instances.push(n); var node = RED.nodes.node(n.id); if (node) { node.changed = n.changed; @@ -258,9 +334,11 @@ RED.history = (function() { var outputMap; if (ev.outputMap) { outputMap = {}; + inverseEv.outputMap = {}; for (var port in ev.outputMap) { if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== "-1") { outputMap[ev.outputMap[port]] = port; + inverseEv.outputMap[ev.outputMap[port]] = port; } } } @@ -268,39 +346,107 @@ RED.history = (function() { RED.editor.validateNode(ev.node); } if (ev.links) { + inverseEv.createdLinks = []; for (i=0;i -1) { + return preferredLangs[i] + } + } + return 'end-US' + }, loadNodeCatalog: function(namespace,done) { var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage()); var toLoad = languageList.length; diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index ad3307ac7..2677321b7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -30,7 +30,8 @@ "backspace": "core:delete-config-selection", "delete": "core:delete-config-selection", "ctrl-a": "core:select-all-config-nodes", - "ctrl-z": "core:undo" + "ctrl-z": "core:undo", + "ctrl-y": "core:redo" }, "red-ui-workspace": { "backspace": "core:delete-selection", @@ -40,6 +41,7 @@ "ctrl-x": "core:cut-selection-to-internal-clipboard", "ctrl-v": "core:paste-from-internal-clipboard", "ctrl-z": "core:undo", + "ctrl-y": "core:redo", "ctrl-a": "core:select-all-nodes", "shift-?": "core:show-help", "up": "core:move-selection-up", diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 89361ec0c..ef9a88074 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -17,6 +17,8 @@ RED.nodes = (function() { var node_defs = {}; var nodes = []; + var nodeTabMap = {}; + var configNodes = {}; var links = []; var defaultWorkspace; @@ -213,6 +215,11 @@ RED.nodes = (function() { n.i = nextId+1; } nodes.push(n); + if (nodeTabMap[n.z]) { + nodeTabMap[n.z][n.id] = n; + } else { + console.warn("Node added to unknown tab/subflow:",n); + } } RED.events.emit('nodes:add',n); } @@ -246,6 +253,9 @@ RED.nodes = (function() { node = getNode(id); if (node) { nodes.splice(nodes.indexOf(node),1); + if (nodeTabMap[node.z]) { + delete nodeTabMap[node.z][node.id]; + } removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); }); removedLinks.forEach(function(l) {links.splice(links.indexOf(l), 1); }); var updatedConfigNode = false; @@ -291,6 +301,17 @@ RED.nodes = (function() { return {links:removedLinks,nodes:removedNodes}; } + function moveNodeToTab(node, z) { + if (nodeTabMap[node.z]) { + delete nodeTabMap[node.z][node.id]; + } + if (!nodeTabMap[z]) { + nodeTabMap[z] = {}; + } + nodeTabMap[z][node.id] = node; + node.z = z; + } + function removeLink(l) { var index = links.indexOf(l); if (index != -1) { @@ -300,6 +321,8 @@ RED.nodes = (function() { function addWorkspace(ws,targetIndex) { workspaces[ws.id] = ws; + nodeTabMap[ws.id] = {}; + ws._def = RED.nodes.getType('tab'); if (targetIndex === undefined) { workspacesOrder.push(ws.id); @@ -312,6 +335,7 @@ RED.nodes = (function() { } function removeWorkspace(id) { delete workspaces[id]; + delete nodeTabMap[id]; workspacesOrder.splice(workspacesOrder.indexOf(id),1); var removedNodes = []; @@ -357,6 +381,8 @@ RED.nodes = (function() { sf.name = subflowName; } subflows[sf.id] = sf; + nodeTabMap[sf.id] = {}; + RED.nodes.registerType("subflow:"+sf.id, { defaults:{ name:{value:""}, @@ -373,14 +399,14 @@ RED.nodes = (function() { inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, oneditresize: function(size) { - var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); + // var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); var height = size.height; - for (var i=0; idiv.node-input-env-container-row"); - height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); - $("#node-input-env-container").editableList('height',height-80); + // for (var i=0; idiv.node-input-env-container-row"); + // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); + $("ol.red-ui-editor-subflow-env-list").editableList('height',height); }, set:{ module: "node-red" @@ -393,6 +419,7 @@ RED.nodes = (function() { } function removeSubflow(sf) { delete subflows[sf.id]; + delete nodeTabMap[sf.id]; registry.removeNodeType("subflow:"+sf.id); } @@ -1267,12 +1294,13 @@ RED.nodes = (function() { // TODO: supports filter.z|type function filterNodes(filter) { var result = []; + var searchSet = nodes; + if (filter.hasOwnProperty("z") && Object.hasOwnProperty("values") && nodeTabMap.hasOwnProperty(filter.z) ) { + searchSet = Object.values(nodeTabMap[filter.z]); + } - for (var n=0;n= 0 && index < items.length) { + return $(items[index]).data('data'); + } else { + return; + } + }, + indexOf: function(data) { + var items = this.items(); + for (var i=0;i'); + panel.css({ display: "none" }); + panel.appendTo(document.body); + content.appendTo(panel); + var closeCallback; + + function hide() { + $(document).off("mousedown.red-ui-popover-panel-close"); + panel.hide(); + panel.css({ + height: "auto" + }); + panel.remove(); + } + function show(options) { + var closeCallback = options.onclose; + var target = options.target; + var align = options.align || "left"; + + var pos = target.offset(); + var targetWidth = target.width(); + var targetHeight = target.height(); + var panelHeight = panel.height(); + var panelWidth = panel.width(); + + var top = (targetHeight+pos.top); + if (top+panelHeight > $(window).height()) { + top -= (top+panelHeight)-$(window).height() + 5; + } + if (top < 0) { + panelHeight.height(panelHeight+top) + top = 0; + } + if (align === "left") { + panel.css({ + top: top+"px", + left: (pos.left)+"px", + }); + } else if(align === "right") { + panel.css({ + top: top+"px", + left: (pos.left-panelWidth)+"px", + }); + } + panel.slideDown(100); + + $(document).on("mousedown.red-ui-popover-panel-close", function(event) { + if(!$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) { + if (closeCallback) { + closeCallback(); + } + hide(); + } + // if ($(event.target).closest(target).length) { + // event.preventDefault(); + // } + }) + } + return { + container: panel, + show:show, + hide:hide + } } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index daef61f64..7b3ed45f2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -81,7 +81,7 @@ } }, re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"}, - date: {value:"date",label:"timestamp",hasValue:false}, + date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false}, jsonata: { value: "jsonata", label: "expression", @@ -298,7 +298,8 @@ that.uiSelect.addClass('red-ui-typedInput-focus'); }); - this.optionExpandButton = $('').appendTo(this.uiSelect); + this.optionExpandButton = $('').appendTo(this.uiSelect); + this.optionExpandButtonIcon = $('').appendTo(this.optionExpandButton); this.type(this.options.default||this.typeList[0].value); }catch(err) { console.log(err.stack); @@ -336,6 +337,17 @@ menu.css({ height: "auto" }); + + if (menu.opts.multiple) { + var selected = []; + menu.find('input[type="checkbox"]').each(function() { + if ($(this).prop("checked")) { + selected.push($(this).data('value')) + } + }) + menu.callback(selected); + } + if (this.elementDiv.is(":visible")) { this.input.trigger("focus"); } else if (this.optionSelectTrigger.is(":visible")){ @@ -344,10 +356,12 @@ this.selectTrigger.trigger("focus"); } }, - _createMenu: function(opts,callback) { + _createMenu: function(menuOptions,opts,callback) { var that = this; - var menu = $("
").addClass("red-ui-typedInput-options"); - opts.forEach(function(opt) { + var menu = $("
").addClass("red-ui-typedInput-options red-ui-editor-dialog"); + menu.opts = opts; + menu.callback = callback; + menuOptions.forEach(function(opt) { if (typeof opt === 'string') { opt = {value:opt,label:opt}; } @@ -369,12 +383,20 @@ if (!opt.icon && !opt.label) { op.text(opt.value); } + var cb; + if (opts.multiple) { + cb = $('').css("pointer-events","none").data('value',opt.value).prependTo(op).on("mousedown", function(evt) { evt.preventDefault() }); + } op.on("click", function(event) { event.preventDefault(); event.stopPropagation(); - callback(opt.value); - that._hideMenu(menu); + if (!opts.multiple) { + callback(opt.value); + that._hideMenu(menu); + } else { + cb.prop("checked",!cb.prop("checked")); + } }); }); menu.css({ @@ -398,9 +420,6 @@ } evt.stopPropagation(); }) - - - return menu; }, @@ -409,11 +428,22 @@ this.disarmClick = false; return } + if (menu.opts.multiple) { + var selected = {}; + this.value().split(",").forEach(function(f) { + selected[f] = true; + }) + menu.find('input[type="checkbox"]').each(function() { + $(this).prop("checked",selected[$(this).data('value')]) + }) + } + + var that = this; var pos = relativeTo.offset(); var height = relativeTo.height(); var menuHeight = menu.height(); - var top = (height+pos.top-3); + var top = (height+pos.top); if (top+menuHeight > $(window).height()) { top -= (top+menuHeight)-$(window).height()+5; } @@ -423,7 +453,7 @@ } menu.css({ top: top+"px", - left: (2+pos.left)+"px", + left: (pos.left)+"px", }); menu.slideDown(100); this._delay(function() { @@ -471,7 +501,7 @@ this._getLabelWidth(this.selectTrigger, function(labelWidth) { that.elementDiv.css('left',labelWidth+"px"); that.valueLabelContainer.css('left',labelWidth+"px"); - if (that.optionExpandButton.is(":visible")) { + if (that.optionExpandButton.shown) { that.elementDiv.css('right',"22px"); that.valueLabelContainer.css('right',"22px"); } else { @@ -496,7 +526,7 @@ } else { that.optionSelectLabel.css({'left':'0'}) that.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'}); - if (!that.optionExpandButton.is(":visible")) { + if (!that.optionExpandButton.shown) { that.elementDiv.css({'right':0}); that.input.css({ 'border-top-right-radius': '4px', @@ -511,25 +541,35 @@ _updateOptionSelectLabel: function(o) { var opt = this.typeMap[this.propertyType]; this.optionSelectLabel.empty(); - if (o.icon) { - if (o.icon.indexOf("<") === 0) { - $(o.icon).prependTo(this.optionSelectLabel); - } else if (o.icon.indexOf("/") !== -1) { - // url - $('',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel); + if (this.typeMap[this.propertyType].valueLabel) { + if (opt.multiple) { + this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o); } else { - // icon class - $('',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); + this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o.value); + } + } else if (!opt.multiple) { + if (o.icon) { + if (o.icon.indexOf("<") === 0) { + $(o.icon).prependTo(this.optionSelectLabel); + } else if (o.icon.indexOf("/") !== -1) { + // url + $('',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel); + } else { + // icon class + $('',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); + } + } else if (o.label) { + this.optionSelectLabel.text(o.label); + } else { + this.optionSelectLabel.text(o.value); + } + if (opt.hasValue) { + this.optionValue = o.value; + this._resize(); + this.input.trigger('change',this.propertyType,this.value()); } - } else if (o.label) { - this.optionSelectLabel.text(o.label); } else { - this.optionSelectLabel.text(o.value); - } - if (opt.hasValue) { - this.optionValue = o.value; - this._resize(); - this.input.trigger('change',this.propertyType,this.value()); + this.optionSelectLabel.text(o.length+" selected"); } }, _destroy: function() { @@ -558,7 +598,7 @@ if (this.menu) { this.menu.remove(); } - this.menu = this._createMenu(this.typeList, function(v) { that.type(v) }); + this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) }); if (currentType && !this.typeMap.hasOwnProperty(currentType)) { this.type(this.typeList[0].value); } else { @@ -572,38 +612,51 @@ this._resize(); }, value: function(value) { + var that = this; + var opt = this.typeMap[this.propertyType]; if (!arguments.length) { var v = this.input.val(); - if (this.typeMap[this.propertyType].export) { - v = this.typeMap[this.propertyType].export(v,this.optionValue) + if (opt.export) { + v = opt.export(v,this.optionValue) } return v; } else { - var selectedOption; - if (this.typeMap[this.propertyType].options) { - for (var i=0;i',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); } - } else { + } + if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { this.selectLabel.text(opt.label); } if (this.optionMenu) { @@ -646,6 +700,7 @@ if (opt.options) { if (this.optionExpandButton) { this.optionExpandButton.hide(); + this.optionExpandButton.shown = false; } if (this.optionSelectTrigger) { this.optionSelectTrigger.show(); @@ -668,36 +723,50 @@ if (!that.activeOptions.hasOwnProperty(that.optionValue)) { that.optionValue = null; } - this.optionMenu = this._createMenu(opt.options,function(v){ - that._updateOptionSelectLabel(that.activeOptions[v]); - if (!opt.hasValue) { - that.value(that.activeOptions[v].value) - } - }); + var op; if (!opt.hasValue) { - var currentVal = this.input.val(); var validValue = false; - for (var i=0;i'); + var content = opt.expand.content.call(that,container); + var panel = RED.popover.panel(container); + panel.container.css({ + width:that.valueLabelContainer.width() + }); + if (opt.expand.minWidth) { + panel.container.css({ + minWidth: opt.expand.minWidth+"px" + }); + } + panel.show({ + target:that.optionExpandButton, + onclose:content.onclose, + align: "right" + }); + } }) } else { + this.optionExpandButton.shown = false; this.optionExpandButton.hide(); } } + this._trigger("typechange",null,this.propertyType); this.input.trigger('change',this.propertyType,this.value()); } if (!image) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 2e3412e9b..1f9dcfac3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -19,7 +19,6 @@ */ RED.editor = (function() { - var editStack = []; var editing_node = null; var editing_config_node = null; @@ -526,7 +525,7 @@ RED.editor = (function() { } else if (node.type.indexOf("subflow:")===0) { var subflow = RED.nodes.subflow(node.type.substring(8)); label = RED._("subflow.editSubflowInstance",{name:RED.utils.sanitize(subflow.name)}) - } else { + } else if (node._def !== undefined) { if (typeof node._def.paletteLabel !== "undefined") { try { label = RED.utils.sanitize((typeof node._def.paletteLabel === "function" ? node._def.paletteLabel.call(node._def) : node._def.paletteLabel)||""); @@ -546,148 +545,7 @@ RED.editor = (function() { return label; } - function buildEnvForm(container, node) { - var env_container = $('#node-input-env-container'); - env_container - .css({ - 'min-height':'150px', - 'min-width':'450px' - }) - .editableList({ - addItem: function(container, i, opt) { - var row = $('
').appendTo(container); - if (opt.parent) { - $('
', { - class:"uneditable-input", - style: "margin-left: 5px; width: calc(40% - 8px)", - }).appendTo(row).text(opt.name); - } else { - $('', { - class: "node-input-env-name", - type: "text", - style: "margin-left: 5px; width: calc(40% - 8px)", - placeholder: RED._("common.label.name") - }).attr("autocomplete","disable").appendTo(row).val(opt.name); - } - var valueField = $('',{ - class: "node-input-env-value", - type: "text", - style: "margin-left: 5px; width: calc(60% - 8px)" - }).attr("autocomplete","disable").appendTo(row) - - valueField.typedInput({default:'str', - types:['str','num','bool','json','bin','env'] - }); - - valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); - valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); - - var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(container); - $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); - container.parent().addClass("red-ui-editableList-item-removable"); - if (opt.parent) { - if ((opt.value !== undefined) && (opt.value !== opt.parent.value || opt.type !== opt.parent.type)) { - actionButton.show(); - } else { - actionButton.hide(); - } - var restoreTip = RED.popover.tooltip(actionButton,RED._("subflow.env.restore")); - valueField.on("change", function(evt) { - var newType = valueField.typedInput('type'); - var newValue = valueField.typedInput('value'); - if (newType === opt.parent.type && newValue === opt.parent.value) { - actionButton.hide(); - } else { - actionButton.show(); - } - }) - actionButton.on("click", function(evt) { - evt.preventDefault(); - restoreTip.close(); - valueField.typedInput('type', opt.parent.type); - valueField.typedInput('value', opt.parent.value); - }) - } else { - var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); - actionButton.on("click", function(evt) { - evt.preventDefault(); - removeTip.close(); - container.parent().addClass("red-ui-editableList-item-deleting") - container.fadeOut(300, function() { - env_container.editableList('removeItem',opt); - }); - }); - } - }, - sortable: false, - removable: false - }); - var parentEnv = {}; - var envList = []; - if (/^subflow:/.test(node.type)) { - var subflowDef = RED.nodes.subflow(node.type.substring(8)); - if (subflowDef.env) { - subflowDef.env.forEach(function(env) { - var item = { - name:env.name, - parent: { - type: env.type, - value: env.value - } - } - envList.push(item); - parentEnv[env.name] = item; - }) - } - } - - if (node.env) { - for (var i = 0; i < node.env.length; i++) { - var env = node.env[i]; - if (parentEnv.hasOwnProperty(env.name)) { - parentEnv[env.name].type = env.type; - parentEnv[env.name].value = env.value; - } else { - envList.push({ - name: env.name, - type: env.type, - value: env.value - }); - } - } - } - envList.forEach(function(env) { - env_container.editableList('addItem', env); - }) - } - - function exportEnvList(list) { - if (list) { - var env = []; - list.each(function(i) { - var entry = $(this); - var item = entry.data('data'); - var name = (item.parent?item.name:entry.find(".node-input-env-name").val()).trim(); - if (name !== "") { - var valueInput = entry.find(".node-input-env-value"); - var value = valueInput.typedInput("value"); - var type = valueInput.typedInput("type"); - if (!item.parent || (item.parent.value !== value || item.parent.type !== type)) { - var item = { - name: name, - type: type, - value: value - }; - env.push(item); - } - } - }); - return env; - } - return null; - } - - function isSameEnv(env0, env1) { + function isSameObj(env0, env1) { return (JSON.stringify(env0) === JSON.stringify(env1)); } @@ -713,8 +571,8 @@ RED.editor = (function() { $(this).attr("data-i18n",keys.join(";")); }); - if ((type === "subflow") || (type === "subflow-template")) { - buildEnvForm(dialogForm, node); + if (type === "subflow-template" || type === "subflow") { + RED.subflow.buildEditForm(dialogForm,type,node); } // Add dummy fields to prevent 'Enter' submitting the form in some @@ -857,7 +715,7 @@ RED.editor = (function() { } return result; } - function showIconPicker(container, node, iconPath, done) { + function showIconPicker(container, node, iconPath, faOnly, done) { var containerPos = container.offset(); var pickerBackground = $('
').css({ position: "absolute",top:0,bottom:0,left:0,right:0,zIndex:20 @@ -912,7 +770,14 @@ RED.editor = (function() { done(null); }); var iconSets = RED.nodes.getIconSets(); + var backgroundColor = node && RED.utils.getNodeColor(node.type, node._def); + if (!node && faOnly) { + iconList.addClass("red-ui-icon-list-dark"); + } Object.keys(iconSets).forEach(function(moduleName) { + if (faOnly && (moduleName !== "font-awesome")) { + return; + } var icons = iconSets[moduleName]; if (icons.length > 0) { // selectIconModule.append($("").val(moduleName).text(moduleName)); @@ -921,10 +786,13 @@ RED.editor = (function() { icons.forEach(function(icon) { var iconDiv = $('
',{class:"red-ui-icon-list-icon"}).appendTo(iconList); var nodeDiv = $('
',{class:"red-ui-search-result-node"}).appendTo(iconDiv); - var colour = RED.utils.getNodeColor(node.type, node._def); var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon; iconDiv.data('icon',icon_url); - nodeDiv.css('backgroundColor',colour); + if (node) { + nodeDiv.css({ + 'backgroundColor': backgroundColor + }); + } var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); RED.utils.createIconElement(icon_url, iconContainer, true); @@ -1067,8 +935,65 @@ RED.editor = (function() { var i,row; + if (node.type === "subflow") { + var categoryRow = $("
", { + class: "form-row" + }).appendTo(dialogForm); + $("
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + iconClass: "fa fa-list" + }; + + RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node); + editorTabs.addTab(subflowPropertiesTab); + } + if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { var descriptionTab = { id: "editor-tab-description", @@ -1942,7 +1881,6 @@ RED.editor = (function() { var configId = editing_config_node.id; var configAdding = adding; var configTypeDef = RED.nodes.getType(configType); - if (configTypeDef.oneditcancel) { // TODO: what to pass as this to call if (configTypeDef.oneditcancel) { @@ -2286,9 +2224,9 @@ RED.editor = (function() { editing_node.icon = icon; changed = true; } - var newCategory = $("#subflow-input-category").val().trim(); + var newCategory = $("#subflow-appearance-input-category").val().trim(); if (newCategory === "_custom_") { - newCategory = $("#subflow-input-custom-category").val().trim(); + newCategory = $("#subflow-appearance-input-custom-category").val().trim(); if (newCategory === "") { newCategory = editing_node.category; } @@ -2312,8 +2250,8 @@ RED.editor = (function() { } var old_env = editing_node.env; - var new_env = exportEnvList($("#node-input-env-container").editableList("items")); - if (!isSameEnv(old_env, new_env)) { + var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items")); + if (!isSameObj(old_env, new_env)) { editing_node.env = new_env; changes.env = editing_node.env; changed = true; @@ -2359,21 +2297,39 @@ RED.editor = (function() { ], resize: function(size) { $(".red-ui-tray-content").height(size.height - 50); - // var form = $(".red-ui-tray-content form").height(size.height - 50 - 40); - var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); - var height = size.height; - for (var i=0; idiv:not(#subflow-env-tabs-content)"); + var height = size.height; + for (var i=0; idiv.node-input-env-container-row"); + // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); + $("#node-input-env-container").editableList('height',height-95); } - var editorRow = $("#dialog-form>div.node-input-env-container-row"); - height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); - $("#node-input-env-container").editableList('height',height-80); }, open: function(tray) { var trayFooter = tray.find(".red-ui-tray-footer"); + var trayFooterLeft = $("
", { + class: "red-ui-tray-footer-left" + }).appendTo(trayFooter) var trayBody = tray.find('.red-ui-tray-body'); trayBody.parent().css('overflow','hidden'); + if (editing_node.type === "subflow") { + var span = $("").css({ + "margin-left": "10px" + }).appendTo(trayFooterLeft); + $("", { + class: "fa fa-info-circle" + }).appendTo(span); + $("").text(" ").appendTo(span); + $("", { + id: "red-ui-editor-subflow-user-count" + }).appendTo(span); + } if (editing_node) { RED.sidebar.info.refresh(editing_node); @@ -2433,46 +2389,9 @@ RED.editor = (function() { buildAppearanceForm(appearanceTab.content,editing_node); editorTabs.addTab(appearanceTab); - - - $("#subflow-input-name").val(subflow.name); RED.text.bidi.prepareInput($("#subflow-input-name")); - $("#subflow-input-category").empty(); - var categories = RED.palette.getCategories(); - categories.sort(function(A,B) { - return A.label.localeCompare(B.label); - }) - categories.forEach(function(cat) { - $("#subflow-input-category").append($("").val(cat.id).text(cat.label)); - }) - $("#subflow-input-category").append($("").attr('disabled',true).text("---")); - $("#subflow-input-category").append($("").val("_custom_").text(RED._("palette.addCategory"))); - - - $("#subflow-input-category").on("change", function() { - var val = $(this).val(); - if (val === "_custom_") { - $("#subflow-input-category").width(120); - $("#subflow-input-custom-category").show(); - } else { - $("#subflow-input-category").width(250); - $("#subflow-input-custom-category").hide(); - } - }) - - $("#subflow-input-category").val(subflow.category||"subflows"); - var userCount = 0; - var subflowType = "subflow:"+editing_node.id; - - RED.nodes.eachNode(function(n) { - if (n.type === subflowType) { - userCount++; - } - }); - $("#subflow-dialog-user-count").text(RED._("subflow.subflowInstances", {count:userCount})).show(); - trayBody.i18n(); finishedBuilding = true; }, @@ -2625,6 +2544,8 @@ RED.editor = (function() { validateNode: validateNode, updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo + showIconPicker:showIconPicker, + /** * Show a type editor. * @param {string} type - the type to display diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index ae78b2e1d..01503a58d 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -152,7 +152,7 @@ RED.palette = (function() { function getPaletteNode(type) { return $(".red-ui-palette-node[data-palette-type='"+type+"']"); } - + function addNodeType(nt,def) { if (getPaletteNode(nt).length) { return; @@ -241,7 +241,6 @@ RED.palette = (function() { RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp")); }); var chart = $("#red-ui-workspace-chart"); - var chartOffset = chart.offset(); var chartSVG = $("#red-ui-workspace-chart>svg").get(0); var activeSpliceLink; var mouseX; @@ -265,8 +264,8 @@ RED.palette = (function() { ui.originalPosition.left = $('#' + e.target.id).offset().left; if (def.inputs > 0 && def.outputs > 0) { - mouseX = ui.position.left-paletteWidth+(ui.helper.width()/2) - chartOffset.left + chart.scrollLeft(); - mouseY = ui.position.top-paletteTop+(ui.helper.height()/2) - chartOffset.top + chart.scrollTop(); + mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); + mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); if (!spliceTimer) { spliceTimer = setTimeout(function() { var nodes = []; @@ -416,7 +415,7 @@ RED.palette = (function() { createCategory(newCategory,category,category,"node-red"); var currentCategoryNode = paletteNode.closest(".red-ui-palette-category"); - var newCategoryNode = $("#palette-"+category); + var newCategoryNode = $("#red-ui-palette-"+category); newCategoryNode.append(paletteNode); if (newCategoryNode.find(".red-ui-palette-node").length === 1) { categoryContainers[category].open(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 342109200..66d016d92 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -16,18 +16,29 @@ RED.subflow = (function() { + var currentLocale = "en-US"; + var _subflowEditTemplate = ''; var _subflowTemplateEditTemplate = ''; function findAvailableSubflowIOPosition(subflow,isInput) { @@ -725,7 +736,7 @@ RED.subflow = (function() { } n.x -= offsetX; n.y -= offsetY; - n.z = subflow.id; + RED.nodes.moveNodeToTab(n, subflow.id); } RED.history.push({ @@ -749,6 +760,946 @@ RED.subflow = (function() { RED.view.redraw(true); } + + /** + * Create interface for controlling env var UI definition + */ + function buildEnvControl(envList) { + + var tabs = RED.tabs.create({ + id: "subflow-env-tabs", + onchange: function(tab) { + if (tab.id === "subflow-env-tab-preview") { + var inputContainer = $("#subflow-input-ui"); + var list = envList.editableList("items"); + var exportedEnv = exportEnvList(list, true); + buildEnvUI(inputContainer, exportedEnv); + } + $("#subflow-env-tabs-content").children().hide(); + $("#" + tab.id).show(); + } + }); + tabs.addTab({ + id: "subflow-env-tab-edit", + label: RED._("editor-tab.envProperties") + }); + tabs.addTab({ + id: "subflow-env-tab-preview", + label: RED._("editor-tab.preview") + }); + + var localesList = RED.settings.theme("languages") + .map(function(lc) { var name = RED._("languages."+lc); return {text: (name ? name : lc), val: lc}; }) + .sort(function(a, b) { return a.text.localeCompare(b.text) }); + RED.popover.tooltip($(".node-input-env-locales-row i"),RED._("editor.locale")) + var locales = $("#subflow-input-env-locale") + localesList.forEach(function(item) { + var opt = { + value: item.val + }; + if (item.val === "en-US") { // make en-US default selected + opt.selected = ""; + } + $("
').appendTo(container); + var nameField = null; + var valueField = null; + + // if (opt.parent) { + // buildEnvUIRow(envRow,opt,opt.parent.ui||{}) + // } else { + nameField = $('', { + class: "node-input-env-name", + type: "text", + placeholder: RED._("common.label.name") + }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); + valueField = $('',{ + style: "width:100%", + class: "node-input-env-value", + type: "text", + }).attr("autocomplete","disable").appendTo(envRow) + valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); + valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); + valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); + // } + + + opt.nameField = nameField; + opt.valueField = valueField; + + if (!opt.parent) { + var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); + $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); + var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); + actionButton.on("click", function(evt) { + evt.preventDefault(); + removeTip.close(); + container.parent().addClass("red-ui-editableList-item-deleting") + container.fadeOut(300, function() { + envContainer.editableList('removeItem',opt); + }); + }); + } + + if (isTemplateNode) { + // Add the UI customisation row + // if `opt.ui` does not exist, then apply defaults. If these + // defaults do not change then they will get stripped off + // before saving. + opt.ui = opt.ui || { + icon: "", + label: {}, + type: "input", + opts: {types:['str','num','bool','json','bin','env']} + } + opt.ui.label = opt.ui.label || {}; + opt.ui.type = opt.ui.type || "input"; + + var uiRow = $('
').appendTo(container).hide(); + // save current info for reverting on cancel + // var copy = $.extend(true, {}, ui); + + $('').prependTo(envRow).on("click", function (evt) { + evt.preventDefault(); + if ($(this).hasClass('expanded')) { + uiRow.slideUp(); + $(this).removeClass('expanded'); + } else { + uiRow.slideDown(); + $(this).addClass('expanded'); + } + }); + + buildEnvEditRow(uiRow, opt.ui, nameField, valueField); + nameField.trigger('change'); + } + }, + sortable: ".red-ui-editableList-item-handle", + removable: false + }); + var parentEnv = {}; + var envList = []; + if (/^subflow:/.test(node.type)) { + var subflowDef = RED.nodes.subflow(node.type.substring(8)); + if (subflowDef.env) { + subflowDef.env.forEach(function(env) { + var item = { + name:env.name, + parent: { + type: env.type, + value: env.value, + ui: env.ui + } + } + envList.push(item); + parentEnv[env.name] = item; + }) + } + } + + if (node.env) { + for (var i = 0; i < node.env.length; i++) { + var env = node.env[i]; + if (parentEnv.hasOwnProperty(env.name)) { + parentEnv[env.name].type = env.type; + parentEnv[env.name].value = env.value; + } else { + envList.push({ + name: env.name, + type: env.type, + value: env.value, + ui: env.ui + }); + } + } + } + envList.forEach(function(env) { + if (env.parent && env.parent.ui && env.parent.ui.type === 'hide') { + return; + } + if (!isTemplateNode && env.parent) { + return; + } + envContainer.editableList('addItem', JSON.parse(JSON.stringify(env))); + }); + } + + /** + * Create UI edit interface for environment variable + * @param container - container + * @param env - env var definition + * @param nameField - name field of env var + * @param valueField - value field of env var + */ + function buildEnvEditRow(container, ui, nameField, valueField) { + container.addClass("red-ui-editor-subflow-env-ui-row") + var topRow = $('
').appendTo(container); + $('
').appendTo(topRow); + $('
').text(RED._("editor.icon")).appendTo(topRow); + $('
').text(RED._("editor.label")).appendTo(topRow); + $('
').text(RED._("editor.inputType")).appendTo(topRow); + + var row = $('
').appendTo(container); + $('
').appendTo(row); + + var typeOptions = { + 'input': {types:['str','num','bool','json','bin','env']}, + 'select': {opts:[]}, + 'spinner': {} + }; + if (ui.opts) { + typeOptions[ui.type] = ui.opts; + } else { + // Pick up the default values if not otherwise provided + ui.opts = typeOptions[ui.type]; + } + var iconCell = $('
').appendTo(row); + + var iconButton = $('
').appendTo(iconCell); + iconButton.on("click", function(evt) { + evt.preventDefault(); + var icon = ui.icon || ""; + var iconPath = (icon ? RED.utils.separateIconPath(icon) : {}); + RED.editor.showIconPicker(row, null, iconPath, true, function (newIcon) { + iconButton.empty(); + var path = newIcon || ""; + var newPath = RED.utils.separateIconPath(path); + if (newPath) { + $('').addClass(newPath.file).appendTo(iconButton); + } + ui.icon = path; + }); + }) + + if (ui.icon) { + var newPath = RED.utils.separateIconPath(ui.icon); + $('').appendTo(iconButton); + } + + var labelCell = $('
').appendTo(row); + + var label = ui.label && ui.label[currentLocale] || ""; + var labelInput = $('').val(label).appendTo(labelCell); + ui.labelField = labelInput; + labelInput.on('change', function(evt) { + ui.label = ui.label || {}; + var val = $(this).val().trim(); + if (val === "") { + delete ui.label[currentLocale]; + } else { + ui.label[currentLocale] = val; + } + }) + var labelIcon = $('').appendTo(labelCell); + RED.popover.tooltip(labelIcon,function() { + var langs = Object.keys(ui.label); + var content = $("
"); + if (langs.indexOf(currentLocale) === -1) { + langs.push(currentLocale); + langs.sort(); + } + langs.forEach(function(l) { + var row = $('
').appendTo(content); + $('').css({display:"inline-block",width:"50px"}).text(l+(l===currentLocale?"*":"")).appendTo(row); + $('').text(ui.label[l]||"").appendTo(row); + }); + return content; + }) + + nameField.on('change',function(evt) { + labelInput.attr("placeholder",$(this).val()) + }); + + var inputCell = $('
').appendTo(row); + var inputCellInput = $('').css("width","100%").appendTo(inputCell); + if (ui.type === "input") { + inputCellInput.val(ui.opts.types.join(",")); + } + var checkbox; + var selectBox; + + inputCellInput.typedInput({ + types: [ + {value:"input", + label:"input", icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ + {value:"str",label:"string",icon:"red/images/typedInput/az.svg"}, + {value:"num",label:"number",icon:"red/images/typedInput/09.svg"}, + {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg"}, + {value:"json",label:"JSON",icon:"red/images/typedInput/json.svg"}, + {value: "bin",label: "buffer",icon: "red/images/typedInput/bin.svg"}, + {value: "env",label: "env variable",icon: "red/images/typedInput/env.svg"} + ], + default: ['str','num','bool','json','bin','env'], + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); + + var input = $('
').appendTo(innerContainer); + $('').appendTo(input); + if (value.length) { + value.forEach(function(v) { + $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); + }) + } else { + $("").css({ + "color":"#aaa", + "padding-left": "4px" + }).text("select types...").appendTo(input); + } + } + }, + {value:"select", + label:"select", icon:"fa fa-tasks",showLabel:false, + valueLabel: function(container,value) { + container.css("padding","0"); + + selectBox = $('').appendTo(container); + if (ui.opts && Array.isArray(ui.opts.opts)) { + ui.opts.opts.forEach(function(o) { + var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); + // $('