diff --git a/.github/scripts/update-node-red-website.js b/.github/scripts/update-node-red-website.js new file mode 100644 index 000000000..cc40c2996 --- /dev/null +++ b/.github/scripts/update-node-red-website.js @@ -0,0 +1,18 @@ +const fs = require("fs"); + +const newVersion = require("../../package.json").version; + +if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) { + console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`); + process.exit(0); +} + +if (!/^\d+\.\d+\.\d+$/.test(newVersion)) { + console.log(`Not updating for a non-stable release - ${newVersion}`); + process.exit(0); +} + +const path = __dirname+"/../../../node-red.github.io/index.html"; +let contents = fs.readFileSync(path, "utf8"); +contents = contents.replace(/v\d+\.\d+\.\d+<\/span>/, `v${newVersion}<\/span>` ); +fs.writeFileSync(path, contents); \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 356d8d71f..43b0137c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,12 +18,16 @@ jobs: with: repository: 'node-red/node-red-docker' path: 'node-red-docker' + - name: Check out node-red.github.io repository + uses: actions/checkout@v2 + with: + repository: 'node-red/node-red.github.io' + path: 'node-red.github.io' - uses: actions/setup-node@v1 with: node-version: '12' - run: node ./node-red/.github/scripts/update-node-red-docker.js - id: updateFiles - - name: Create Pull Request + - name: Create Docker Pull Request uses: peter-evans/create-pull-request@v2 with: token: ${{ secrets.NR_REPO_TOKEN }} @@ -37,4 +41,18 @@ jobs: Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`. + This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary + - run: node ./node-red/.github/scripts/update-node-red-website.js + - name: Create Website Pull Request + uses: peter-evans/create-pull-request@v2 + with: + token: ${{ secrets.NR_REPO_TOKEN }} + committer: GitHub + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + path: 'node-red.github.io' + commit-message: 'Bump to ${{ env.newVersion }}' + title: '🚀 Update to Node-RED ${{ env.newVersion }} release' + body: | + Updates the Node-RED Website repo for the ${{ env.newVersion }} release. + This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary diff --git a/CHANGELOG.md b/CHANGELOG.md index a24f89bc8..0d4baaf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +### 1.1.3: Maintenance Release + +Editor + - Fix vertical align of fa node icons Fixes #2670 + - Allow lasso selection to be restricted to active group + - Make ctrl-click on nested group more intuitive + - Fix copy/paste of nested groups + - Add Set(iterable) polyfill for IE11 + - Support select-all inside active group + - Improve performance of moving groups + - Add additional check for git auth failure response Fixes #2656 + - german translation, wording (#2660) (#2666) + - Remove filtering of duplicate fa icons + - Show node help when switching node edit dialogs Fixes #2652 + - Ensure group theme picks up theme defaults properly Fixes #2651 + +Nodes + - Clarify Switch node isEmpty help + - HTTP In: handle application/cbor as binary + +Runtime + - Move runtime settings back to adminApi from editorApi Fixes #2662 + - Update Chinese message for debug node + +### 1.1.2: Maintenance Release + +Editor + + - Fix all the touch screen issues Fixes #2647 + - Add RED.view.redrawStatus to avoid full redraw on update + - Ensure node/group xrefs are consistent on import + - Disable keyboard handler when dialogs are open + - Ensure unknown nodes removed from outliner when node registers Fixes #2646 + +Runtime + + - Allow Comms websocket auth to be done via token header Fixes #2642 + ### 1.1.1: Maintenance Release Editor 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 50d7b168f..d982c560d 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 @@ -21,15 +21,17 @@ var flows = require("./flows"); var flow = require("./flow"); var context = require("./context"); var auth = require("../auth"); +var info = require("./settings"); var apiUtil = require("../util"); module.exports = { - init: function(runtimeAPI) { + init: function(settings,runtimeAPI) { flows.init(runtimeAPI); flow.init(runtimeAPI); nodes.init(runtimeAPI); context.init(runtimeAPI); + info.init(settings,runtimeAPI); var needsPermission = auth.needsPermission; @@ -67,6 +69,8 @@ module.exports = { // adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler); adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler); + adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); + return adminApp; } } diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/settings.js b/packages/node_modules/@node-red/editor-api/lib/admin/settings.js new file mode 100644 index 000000000..d72f9e094 --- /dev/null +++ b/packages/node_modules/@node-red/editor-api/lib/admin/settings.js @@ -0,0 +1,72 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ +var apiUtils = require("../util"); +var runtimeAPI; +var settings; +var theme = require("../editor/theme"); +var clone = require("clone"); + +var i18n = require("@node-red/util").i18n + +function extend(target, source) { + var keys = Object.keys(source); + var i = keys.length; + while(i--) { + var value = source[keys[i]] + var type = typeof value; + if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) { + target[keys[i]] = value; + } else if (value === null) { + if (target.hasOwnProperty(keys[i])) { + delete target[keys[i]]; + } + } else { + // Object + if (target.hasOwnProperty(keys[i])) { + target[keys[i]] = extend(target[keys[i]],value); + } else { + target[keys[i]] = value; + } + } + } + return target; +} + +module.exports = { + init: function(_settings,_runtimeAPI) { + runtimeAPI = _runtimeAPI; + settings = _settings; + }, + runtimeSettings: function(req,res) { + var opts = { + user: req.user + } + runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) { + if (!settings.disableEditor) { + result.editorTheme = result.editorTheme||{}; + var themeSettings = theme.settings(); + if (themeSettings) { + // result.editorTheme may already exist with the palette + // disabled. Need to merge that into the receive settings + result.editorTheme = extend(clone(themeSettings),result.editorTheme); + } + result.editorTheme.languages = i18n.availableLanguages("editor"); + } + res.json(result); + }); + }, + +} diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index 87023a487..bae4df5c3 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,38 +123,57 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } + +function authenticateUserToken(req) { + return new Promise( (resolve,reject) => { + var token = null; + var tokenHeader = Users.tokenHeader(); + if (Users.tokenHeader() === null) { + // No custom user token provided. Fail the request + reject(); + return; + } else if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } + if (token) { + Users.tokens(token).then(function(user) { + if (user) { + resolve(user); + } else { + reject(); + } + }); + } else { + reject(); + } + }); +} + + function TokensStrategy() { passport.Strategy.call(this); this.name = 'tokens'; } util.inherits(TokensStrategy, passport.Strategy); TokensStrategy.prototype.authenticate = function(req) { - var self = this; - var token = null; - if (Users.tokenHeader() === 'authorization') { - if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { - token = req.headers.authorization.split(' ')[1]; - } - } else { - token = req.headers[Users.tokenHeader()]; - } - if (token) { - Users.tokens(token).then(function(admin) { - if (admin) { - self.success(admin,{scope:admin.permissions}); - } else { - self.fail(401); - } - }); - } else { - self.fail(401); - } + authenticateUserToken(req).then(user => { + this.success(user,{scope:user.permissions}); + }).catch(err => { + this.fail(401); + }); } + + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, anonymousStrategy: new AnonymousStrategy(), - tokensStrategy: new TokensStrategy() + tokensStrategy: new TokensStrategy(), + authenticateUserToken: authenticateUserToken } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index f032332db..b5754bee9 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -61,7 +61,7 @@ var api = { authenticate: authenticate, default: getDefaultUser, tokens: getDefaultUser, - tokenHeader: "authorization" + tokenHeader: null } function init(config) { @@ -111,6 +111,8 @@ function init(config) { api.tokens = config.tokens; if (config.tokenHeader && typeof config.tokenHeader === "string") { api.tokenHeader = config.tokenHeader.toLowerCase(); + } else { + api.tokenHeader = "authorization"; } } } diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/comms.js b/packages/node_modules/@node-red/editor-api/lib/editor/comms.js index 2c46f87e8..84da62a69 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/comms.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/comms.js @@ -21,6 +21,7 @@ var log = require("@node-red/util").log; // TODO: separate module var Tokens; var Users; var Permissions; +var Strategies; var server; var settings; @@ -44,6 +45,7 @@ function init(_server,_settings,_runtimeAPI) { Tokens.onSessionExpiry(handleSessionExpiry); Users = require("../auth/users"); Permissions = require("../auth/permissions"); + Strategies = require("../auth/strategies"); } function handleSessionExpiry(session) { @@ -63,17 +65,18 @@ function generateSession(length) { return token.join(""); } -function CommsConnection(ws) { +function CommsConnection(ws, user) { this.session = generateSession(32); this.ws = ws; this.stack = []; - this.user = null; + this.user = user; this.lastSentTime = 0; var self = this; log.audit({event: "comms.open"}); log.trace("comms.open "+self.session); - var pendingAuth = (settings.adminAuth != null); + var preAuthed = !!user; + var pendingAuth = !this.user && (settings.adminAuth != null); if (!pendingAuth) { addActiveConnection(self); @@ -199,8 +202,8 @@ function start() { var commsPath = settings.httpAdminRoot || "/"; commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms"; wsServer = new ws.Server({ noServer: true }); - wsServer.on('connection',function(ws) { - var commsConnection = new CommsConnection(ws); + wsServer.on('connection',function(ws, request, user) { + var commsConnection = new CommsConnection(ws, user); }); wsServer.on('error', function(err) { log.warn(log._("comms.error-server",{message:err.toString()})); @@ -209,8 +212,26 @@ function start() { server.on('upgrade', function upgrade(request, socket, head) { const pathname = url.parse(request.url).pathname; if (pathname === commsPath) { + if (Users.tokenHeader() !== null && request.headers[Users.tokenHeader()]) { + // The user has provided custom token handling. For the websocket, + // the token could be provided in two ways: + // - as an http header (only possible with a reverse proxy setup) + // - passed over the connected websock in an auth packet + // If the header is present, verify the token. If not, use the auth + // packet over the connected socket + // + Strategies.authenticateUserToken(request).then(user => { + wsServer.handleUpgrade(request, socket, head, function done(ws) { + wsServer.emit('connection', ws, request, user); + }); + }).catch(err => { + log.audit({event: "comms.auth.fail"}); + socket.destroy(); + }) + return + } wsServer.handleUpgrade(request, socket, head, function done(ws) { - wsServer.emit('connection', ws, request); + wsServer.emit('connection', ws, request, null); }); } // Don't destroy the socket as other listeners may want to handle the diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index a9cdf0ffc..71876eaa6 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -103,7 +103,7 @@ module.exports = { editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler); // Settings - editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); + // Main /settings route is an admin route - see lib/admin/settings.js // User Settings editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler); // User Settings diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/settings.js b/packages/node_modules/@node-red/editor-api/lib/editor/settings.js index 9d9867f1b..5fa2476e1 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/settings.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/settings.js @@ -16,56 +16,12 @@ var apiUtils = require("../util"); var runtimeAPI; var sshkeys = require("./sshkeys"); -var theme = require("./theme"); -var clone = require("clone"); - -var i18n = require("@node-red/util").i18n - -function extend(target, source) { - var keys = Object.keys(source); - var i = keys.length; - while(i--) { - var value = source[keys[i]] - var type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) { - target[keys[i]] = value; - } else if (value === null) { - if (target.hasOwnProperty(keys[i])) { - delete target[keys[i]]; - } - } else { - // Object - if (target.hasOwnProperty(keys[i])) { - target[keys[i]] = extend(target[keys[i]],value); - } else { - target[keys[i]] = value; - } - } - } - return target; -} module.exports = { init: function(_runtimeAPI) { runtimeAPI = _runtimeAPI; sshkeys.init(runtimeAPI); }, - runtimeSettings: function(req,res) { - var opts = { - user: req.user - } - runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) { - result.editorTheme = result.editorTheme||{}; - var themeSettings = theme.settings(); - if (themeSettings) { - // result.editorTheme may already exist with the palette - // disabled. Need to merge that into the receive settings - result.editorTheme = extend(clone(themeSettings),result.editorTheme); - } - result.editorTheme.languages = i18n.availableLanguages("editor"); - res.json(result); - }); - }, userSettings: function(req, res) { var opts = { user: req.user diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 534a77869..457b99cc9 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -99,7 +99,7 @@ function init(settings,_server,storage,runtimeAPI) { adminApp.use(corsHandler); } - var adminApiApp = require("./admin").init(runtimeAPI); + var adminApiApp = require("./admin").init(settings, runtimeAPI); adminApp.use(adminApiApp); } else { adminApp = null; diff --git a/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js b/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js index c97e09c66..8811f1514 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js +++ b/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js @@ -808,17 +808,7 @@ RED.nodes.fontAwesome = (function() { "fa-youtube": "\uf167", }; - var iconList = []; - var isUsed = {}; - Object.keys(iconMap).forEach(function(icon) { - var unicode = iconMap[icon]; - // skip icons with a same unicode - if (isUsed[unicode] !== true) { - iconList.push(icon); - isUsed[unicode] = true; - } - }); - isUsed = undefined; + var iconList = Object.keys(iconMap); return { getIconUnicode: function(name) { 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 ee7e7413f..84499ffe6 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 @@ -998,6 +998,7 @@ RED.nodes = (function() { var new_nodes = []; var new_links = []; var new_groups = []; + var new_group_set = new Set(); var nid; var def; var configNode; @@ -1326,6 +1327,7 @@ RED.nodes = (function() { new_nodes.push(node); } else if (node.type === "group") { new_groups.push(node); + new_group_set.add(node.id); } } } @@ -1433,16 +1435,23 @@ RED.nodes = (function() { var groupDepthMap = {}; for (i=0;i1) { clearTimeout(touchStartTime); @@ -211,6 +260,7 @@ RED.view = (function() { d3.event.preventDefault(); return; } + if (RED.view.DEBUG) { console.warn("eventLayer.touchmove", mouse_mode, mousedown_node); } var touch0; if (d3.event.touches.length<2) { if (touchStartTime) { @@ -221,6 +271,12 @@ RED.view = (function() { if (d > 64) { clearTimeout(touchStartTime); touchStartTime = null; + if (!mousedown_node && !mousedown_group) { + mouse_mode = RED.state.PANNING; + mouse_position = [touch0.pageX,touch0.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + } + } } else if (lasso) { d3.event.preventDefault(); @@ -419,7 +475,7 @@ RED.view = (function() { exitActiveGroup(); clearSelection(); nn.selected = true; - moving_set.push({n:nn}); + movingSet.add(nn); if (group) { selectGroup(group,false); enterActiveGroup(group); @@ -1099,7 +1155,7 @@ RED.view = (function() { selectGroup(targetGroup,false); enterActiveGroup(targetGroup); } - moving_set.push({n:nn}); + movingSet.add(nn); updateActiveNodes(); updateSelection(); redraw(); @@ -1156,8 +1212,11 @@ RED.view = (function() { //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); if (mouse_mode === RED.state.PANNING) { - var pos = [d3.event.pageX,d3.event.pageY]; + if (d3.event.touches) { + var touch0 = d3.event.touches.item(0); + pos = [touch0.pageX, touch0.pageY]; + } var deltaPos = [ mouse_position[0]-pos[0], mouse_position[1]-pos[1] @@ -1293,12 +1352,12 @@ RED.view = (function() { mousePos = d3.touches(document.body)[0]; } var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]); - if (d > 3) { + if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { mouse_mode = RED.state.MOVING_ACTIVE; clickElapsed = 0; spliceActive = false; - if (moving_set.length === 1) { - node = moving_set[0]; + if (movingSet.length() === 1) { + node = movingSet.get(0); spliceActive = node.n.hasOwnProperty("_def") && ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && @@ -1312,8 +1371,8 @@ RED.view = (function() { var minY = 0; var maxX = space_width; var maxY = space_height; - for (var n = 0; n 0) { + if (snapGrid != d3.event.shiftKey && movingSet.length() > 0) { var i = 0; // Prefer to snap nodes to the grid if there is one in the selection do { - node = moving_set[i++]; - } while(i ag.x && y < ag.y+ag.h && y2 > ag.y) { + // There was an active group and the lasso intersects with it, + // so reenter the group + enterActiveGroup(ag); + activeGroup.selected = true; + } + } } activeGroups.forEach(function(g) { if (!g.selected) { if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { - while (g.g) { - g = RED.nodes.group(g.g); - } - if (!g.selected) { - selectGroup(g,true); + if (!activeGroup || RED.group.contains(activeGroup,g)) { + while (g.g && (!activeGroup || g.g !== activeGroup.id)) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } } } } @@ -1515,18 +1588,21 @@ RED.view = (function() { activeNodes.forEach(function(n) { if (!n.selected) { if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { - if (n.g) { - var group = RED.nodes.group(n.g); - while (group.g) { - group = RED.nodes.group(group.g); + if (!activeGroup || RED.group.contains(activeGroup,n)) { + if (n.g && (!activeGroup || n.g !== activeGroup.id)) { + console.log("HERE") + var group = RED.nodes.group(n.g); + while (group.g && (!activeGroup || group.g !== activeGroup.id)) { + group = RED.nodes.group(group.g); + } + if (!group.selected) { + selectGroup(group,true); + } + } else { + n.selected = true; + n.dirty = true; + movingSet.add(n); } - if (!group.selected) { - selectGroup(group,true); - } - } else { - n.selected = true; - n.dirty = true; - moving_set.push({n:n}); } } } @@ -1550,21 +1626,21 @@ RED.view = (function() { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } }); activeSubflow.out.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } }); if (activeSubflow.status) { activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); if (activeSubflow.status.selected) { activeSubflow.status.dirty = true; - moving_set.push({n:activeSubflow.status}); + movingSet.add(activeSubflow.status); } } } @@ -1576,11 +1652,11 @@ RED.view = (function() { updateSelection(); } if (mouse_mode == RED.state.MOVING_ACTIVE) { - if (moving_set.length > 0) { + if (movingSet.length() > 0) { var addedToGroup = null; if (activeHoverGroup) { - for (var j=0;j 0) { - var node = moving_set[0].n; + if (movingSet.length() > 0) { + var node = movingSet.get(0).n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); } else if (node.type === "group") { @@ -1936,7 +2034,7 @@ RED.view = (function() { updateActiveNodes(); updateSelection(); redraw(); - } else if (moving_set.length > 0 || selected_link != null) { + } else if (movingSet.length() > 0 || selected_link != null) { var result; var node; var removedNodes = []; @@ -1950,10 +2048,10 @@ RED.view = (function() { var startDirty = RED.nodes.dirty(); var startChanged = false; var selectedGroups = []; - if (moving_set.length > 0) { + if (movingSet.length() > 0) { - for (var i=0;i 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) { RED.nodes.dirty(true); } @@ -2681,12 +2779,13 @@ RED.view = (function() { function prepareDrag(mouse) { mouse_mode = RED.state.MOVING; - // Called when moving_set should be prepared to be dragged - for (i=0;i 0 && clickElapsed < 750) { + if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; if (d.type != "subflow") { RED.editor.edit(d); @@ -2721,7 +2820,7 @@ RED.view = (function() { enterActiveGroup(RED.nodes.group(d.g)) mousedown_node.selected = true; - moving_set.push({n:mousedown_node}); + movingSet.add(mousedown_node); var mouse = d3.touches(this)[0]||d3.mouse(this); mouse[0] += d.x-d.w/2; mouse[1] += d.y-d.h/2; @@ -2778,10 +2877,10 @@ RED.view = (function() { var link1 = { source:spliceLink.source, sourcePort:spliceLink.sourcePort, - target: moving_set[0].n + target: movingSet.get(0).n }; var link2 = { - source:moving_set[0].n, + source:movingSet.get(0).n, sourcePort:0, target: spliceLink.target }; @@ -2810,16 +2909,11 @@ RED.view = (function() { } if (d.selected) { d.selected = false; - for (i=0;i 0 && clickElapsed < 750) { + if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; RED.editor.editGroup(g); d3.event.stopPropagation(); @@ -3150,8 +3275,9 @@ RED.view = (function() { dblClickPrimed = ( lastClickNode == g && - d3.event.button === 0 && - !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey + (d3.event.touches || d3.event.button === 0) && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && + clickElapsed < dblClickInterval ); lastClickNode = g; @@ -3164,7 +3290,12 @@ RED.view = (function() { } else { if (!g.selected) { if (!d3.event.ctrlKey && !d3.event.metaKey) { + var ag = activeGroup; clearSelection(); + if (ag && g.g === ag.id) { + enterActiveGroup(ag); + activeGroup.selected = true; + } } if (activeGroup) { if (!RED.group.contains(activeGroup,g)) { @@ -3174,7 +3305,7 @@ RED.view = (function() { } } selectGroup(g,true);//!wasSelected); - } else { + } else if (activeGroup && g.g !== activeGroup.id){ exitActiveGroup(); } @@ -3198,14 +3329,14 @@ RED.view = (function() { g.dirty = true; } if (addToMovingSet !== false) { - moving_set.push({n:g}); + movingSet.add(g); } if (includeNodes) { - var currentSet = new Set(moving_set.map(function(n) { return n.n })); + var currentSet = new Set(movingSet.nodes()); var allNodes = RED.group.getNodes(g,true); allNodes.forEach(function(n) { if (!currentSet.has(n)) { - moving_set.push({n:n}) + movingSet.add(n) // n.selected = true; } n.dirty = true; @@ -3219,12 +3350,7 @@ RED.view = (function() { group.active = true; group.dirty = true; activeGroup = group; - for (var i = moving_set.length-1; i >= 0; i -= 1) { - if (moving_set[i].n === group) { - moving_set.splice(i,1); - break; - } - } + movingSet.remove(group); } function exitActiveGroup() { if (activeGroup) { @@ -3242,11 +3368,12 @@ RED.view = (function() { } var nodeSet = new Set(g.nodes); nodeSet.add(g); - for (var i = moving_set.length-1; i >= 0; i -= 1) { - if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) { - moving_set[i].n.selected = false; - moving_set[i].n.dirty = true; - moving_set.splice(i,1); + for (var i = movingSet.length()-1; i >= 0; i -= 1) { + var msn = movingSet.get(i); + if (nodeSet.has(msn.n) || msn.n === g) { + msn.n.selected = false; + msn.n.dirty = true; + movingSet.remove(msn.n,i) } } } @@ -3330,11 +3457,11 @@ RED.view = (function() { function showTouchMenu(obj,pos) { var mdn = mousedown_node; var options = []; - options.push({name:"delete",disabled:(moving_set.length===0 && selected_link === null),onselect:function() {deleteSelection();}}); - options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}}); - options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}}); + options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}}); + options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,false,true);}}); - options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}}); + options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); options.push({name:"select",onselect:function() {selectAll();}}); options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); options.push({name:"add",onselect:function() { @@ -3424,6 +3551,37 @@ RED.view = (function() { } } + function redrawStatus(d,nodeEl) { + if (d.z !== RED.workspaces.active()) { + return; + } + if (!nodeEl) { + nodeEl = document.getElementById(d.id); + } + if (nodeEl) { + if (!showStatus || !d.status) { + nodeEl.__statusGroup__.style.display = "none"; + } else { + nodeEl.__statusGroup__.style.display = "inline"; + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + nodeEl.__statusShape__.style.display = "none"; + nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); + } else { + nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); + var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; + nodeEl.__statusShape__.style.display = "inline"; + nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); + } + if (d.status.hasOwnProperty('text')) { + nodeEl.__statusLabel__.textContent = d.status.text; + } else { + nodeEl.__statusLabel__.textContent = ""; + } + } + delete d.dirtyStatus; + } + } var pendingRedraw; @@ -3774,9 +3932,11 @@ RED.view = (function() { //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + // This might be the first redraw after a node has been click-dragged to start a move. + // So its selected state might have changed since the last redraw. + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) if (mouse_mode != RED.state.MOVING_ACTIVE) { this.classList.toggle("red-ui-flow-node-disabled", d.d === true); - this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) this.__mainRect__.setAttribute("width", d.w) this.__mainRect__.setAttribute("height", d.h) this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); @@ -3992,27 +4152,7 @@ RED.view = (function() { } if (d.dirtyStatus) { - if (!showStatus || !d.status) { - this.__statusGroup__.style.display = "none"; - } else { - this.__statusGroup__.style.display = "inline"; - var fill = status_colours[d.status.fill]; // Only allow our colours for now - if (d.status.shape == null && fill == null) { - this.__statusShape__.style.display = "none"; - this.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); - } else { - this.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); - var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; - this.__statusShape__.style.display = "inline"; - this.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); - } - if (d.status.hasOwnProperty('text')) { - this.__statusLabel__.textContent = d.status.text; - } else { - this.__statusLabel__.textContent = ""; - } - } - delete d.dirtyStatus; + redrawStatus(d,this); } d.dirty = false; if (d.g) { @@ -4139,7 +4279,7 @@ RED.view = (function() { targets.forEach(function(n) { n.selected = true; n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); }); updateSelection(); redraw(); @@ -4242,6 +4382,8 @@ RED.view = (function() { .attr("y",-4) selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); + selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); @@ -4251,6 +4393,9 @@ RED.view = (function() { "stroke": d.stroke||"none", }) g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + g.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + g.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); + g.append('svg:text').attr("class","red-ui-flow-group-label"); d.dirty = true; }); @@ -4264,7 +4409,9 @@ RED.view = (function() { }) } group[0].reverse(); + var groupOpCount=0; group.each(function(d,i) { + groupOpCount++ if (d.resize) { d.minWidth = 0; delete d.resize; @@ -4272,29 +4419,43 @@ RED.view = (function() { if (d.dirty || dirtyGroups[d.id]) { var g = d3.select(this); if (d.nodes.length > 0) { - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = 0; - var maxY = 0; - var margin = 26; - d.nodes.forEach(function(n) { - if (n.type !== "group") { - minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-margin); - maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+margin); - } else { - minX = Math.min(minX,n.x-margin) - minY = Math.min(minY,n.y-margin) - maxX = Math.max(maxX,n.x+n.w+margin) - maxY = Math.max(maxY,n.y+n.h+margin) - } - }); + // If the group was just moved, all of its contents was + // also moved - so no need to recalculate its bounding box + if (!d.groupMoved) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + var margin = 26; + d.nodes.forEach(function(n) { + groupOpCount++ + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-margin); + maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+margin); + } else { + minX = Math.min(minX,n.x-margin) + minY = Math.min(minY,n.y-margin) + maxX = Math.max(maxX,n.x+n.w+margin) + maxY = Math.max(maxY,n.y+n.h+margin) + } + }); - d.x = minX; - d.y = minY; - d.w = maxX - minX; - d.h = maxY - minY; + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + // if set explicitly to false, this group has just been + // imported so needed this initial resize calculation. + // Now that's done, delete the flag so the normal + // logic kicks in. + if (d.groupMoved === false) { + delete d.groupMoved; + } + } else { + delete d.groupMoved; + } } else { d.w = 40; d.h = 40; @@ -4340,12 +4501,12 @@ RED.view = (function() { var selectGroupRect = selectGroup.children[0]; selectGroupRect.setAttribute("width",d.w+8) selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; selectGroupRect = selectGroup.children[1]; selectGroupRect.setAttribute("width",d.w+8) selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; if (d.highlighted) { @@ -4469,21 +4630,25 @@ RED.view = (function() { if (addNewFlow && new_default_workspace) { RED.workspaces.show(new_default_workspace.id); } - var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); - new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}).map(function(g) { return {n:g}})) + var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); + new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); + + clearSelection(); + movingSet.clear(); + movingSet.add(new_ms); + + // TODO: pick a more sensible root node - if (new_ms.length > 0) { - - + if (movingSet.length() > 0) { if (mouse_position == null) { mouse_position = [0,0]; } var dx = mouse_position[0]; var dy = mouse_position[1]; - if (new_ms.length > 0) { - var root_node = new_ms[0].n; + if (movingSet.length() > 0) { + var root_node = movingSet.get(0).n; dx = root_node.x; dy = root_node.y; } @@ -4492,9 +4657,9 @@ RED.view = (function() { var minY = 0; var i; var node,group; - - for (i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) @@ -4546,8 +4712,6 @@ RED.view = (function() { RED.history.pop(); mouse_mode = 0; }); - clearSelection(); - moving_set = new_ms; } var historyEvent = { @@ -4559,7 +4723,7 @@ RED.view = (function() { subflows:new_subflows, dirty:RED.nodes.dirty() }; - if (new_ms.length === 0) { + if (movingSet.length() === 0) { RED.nodes.dirty(true); } if (activeSubflow) { @@ -4644,10 +4808,10 @@ RED.view = (function() { var changed = false; if (workspaceSelection.length > 0) { // TODO: toggle workspace state - } else if (moving_set.length > 0) { + } else if (movingSet.length() > 0) { var historyEvents = []; - for (var i=0;i 0) { - moving_set.forEach(function(n) { + if (movingSet.length() > 0) { + movingSet.forEach(function(n) { if (n.n.type !== 'group') { allNodes.add(n.n); } @@ -4748,12 +4912,13 @@ RED.view = (function() { if (selectedNode) { selectedNode.selected = true; selectedNode.dirty = true; - moving_set = [{n:selectedNode}]; + movingSet.clear(); + movingSet.add(selectedNode); } } else if (selection) { if (selection.nodes) { updateActiveNodes(); - moving_set = []; + movingSet.clear(); // TODO: this selection group span groups // - if all in one group -> activate the group // - if in multiple groups (or group/no-group) @@ -4762,7 +4927,7 @@ RED.view = (function() { if (n.type !== "group") { n.selected = true; n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } else { selectGroup(n,true); } @@ -4869,7 +5034,7 @@ RED.view = (function() { if (n) { n.selected = true; n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } }) } @@ -4905,7 +5070,7 @@ RED.view = (function() { text: RED._("common.label.done"), class: "primary", click: function(e) { - var selection = moving_set.map(function(n) { return n.n;}); + var selection = movingSet.nodes() selectNodesOptions.done(selection); } }); @@ -4928,6 +5093,7 @@ RED.view = (function() { }, clipboard: function() { return clipboard - } + }, + redrawStatus: redrawStatus }; })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/user.js b/packages/node_modules/@node-red/editor-client/src/js/user.js index 485670e4f..dd0ff8f90 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/user.js +++ b/packages/node_modules/@node-red/editor-client/src/js/user.js @@ -39,7 +39,11 @@ RED.user = (function() { closeOnEscape: !!opts.cancelable, width: 600, resizable: false, - draggable: false + draggable: false, + close: function( event, ui ) { + $("#node-dialog-login").dialog('destroy').remove(); + RED.keyboard.enable() + } }); $("#node-dialog-login-fields").empty(); @@ -98,10 +102,10 @@ RED.user = (function() { data: body }).done(function(data,textStatus,xhr) { RED.settings.set("auth-tokens",data); - $("#node-dialog-login").dialog('destroy').remove(); if (opts.updateMenu) { updateUserMenu(); } + $("#node-dialog-login").dialog("close"); done(); }).fail(function(jqXHR,textStatus,errorThrown) { RED.settings.remove("auth-tokens"); @@ -143,7 +147,8 @@ RED.user = (function() { } if (opts.cancelable) { $("#node-dialog-login-cancel").button().on("click", function( event ) { - $("#node-dialog-login").dialog('destroy').remove(); + $("#node-dialog-login").dialog('close'); + }); } @@ -152,8 +157,7 @@ RED.user = (function() { $("#node-dialog-login-image").load(function() { dialog.dialog("open"); }).attr("src",loginImageSrc); - - + RED.keyboard.disable(); } }); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 4e0aca45b..1f3cdb181 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -597,6 +597,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { padding: 4px; color: $secondary-text-color; font-size: 0.9em; + line-height: 24px; } button { float: right; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 922a31e33..7a2b53eb2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -204,7 +204,7 @@ .red-ui-palette-icon-fa { color: white; position: absolute; - top: 7px; + top: calc(50% - 7px); left: 3px; } .red-ui-palette-node-small { diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 322cb37e9..d1e2941b4 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -46,10 +46,10 @@ module.exports = function(RED) { isText = true; } else if (parsedType.type !== "application") { isText = false; - } else if (parsedType.subtype !== "octet-stream") { + } else if ((parsedType.subtype !== "octet-stream") && (parsedType.subtype !== "cbor")) { checkUTF = true; } else { - // applicatino/octet-stream + // application/octet-stream or application/cbor isText = false; } diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index 58fce3a92..7d9bd3523 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -604,7 +604,7 @@ "inputrange" : "von einem Eingabebereich", "resultrange" : "in einen Zielbereich", "from" : "von", - "to" : "bis", + "to" : "auf", "roundresult" : "Runde das Ergebnis auf die nächste ganze Zahl?" }, "placeholder" : { diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 8355cf353..7c5e50a5d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -123,7 +123,7 @@ "none": "None", "invalid-exp": "无效的JSONata表达式: __error__", "msgprop": "信息属性", - "msgobj": "完整信息", + "msgobj": "与调试输出相同", "autostatus": "自动的", "to": "目标", "debtab": "调试窗口", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 0875cab9b..6f8860de1 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -123,7 +123,7 @@ "none": "None", "invalid-exp": "無效的JSONata表達式: __error__", "msgprop": "資訊屬性", - "msgobj": "完整資訊", + "msgobj": "與調試輸出相同", "autostatus": "自動的", "to": "目標", "debtab": "除錯窗口", diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 96bb6c973..6ebeeccfb 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -81,39 +81,41 @@ var api = module.exports = { }) } - safeSettings.context = runtime.nodes.listContextStores(); + if (!runtime.settings.disableEditor) { + safeSettings.context = runtime.nodes.listContextStores(); - if (util.isArray(runtime.settings.paletteCategories)) { - safeSettings.paletteCategories = runtime.settings.paletteCategories; - } + if (util.isArray(runtime.settings.paletteCategories)) { + safeSettings.paletteCategories = runtime.settings.paletteCategories; + } - if (runtime.settings.flowFilePretty) { - safeSettings.flowFilePretty = runtime.settings.flowFilePretty; - } + if (runtime.settings.flowFilePretty) { + safeSettings.flowFilePretty = runtime.settings.flowFilePretty; + } - if (!runtime.nodes.paletteEditorEnabled()) { - safeSettings.editorTheme = safeSettings.editorTheme || {}; - safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; - safeSettings.editorTheme.palette.editable = false; - } - if (runtime.storage.projects) { - var activeProject = runtime.storage.projects.getActiveProject(); - if (activeProject) { - safeSettings.project = activeProject; - } else if (runtime.storage.projects.flowFileExists()) { - safeSettings.files = { - flow: runtime.storage.projects.getFlowFilename(), - credentials: runtime.storage.projects.getCredentialsFilename() + if (!runtime.nodes.paletteEditorEnabled()) { + safeSettings.editorTheme = safeSettings.editorTheme || {}; + safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; + safeSettings.editorTheme.palette.editable = false; + } + if (runtime.storage.projects) { + var activeProject = runtime.storage.projects.getActiveProject(); + if (activeProject) { + safeSettings.project = activeProject; + } else if (runtime.storage.projects.flowFileExists()) { + safeSettings.files = { + flow: runtime.storage.projects.getFlowFilename(), + credentials: runtime.storage.projects.getCredentialsFilename() + } + } + safeSettings.git = { + globalUser: runtime.storage.projects.getGlobalGitUser() } } - safeSettings.git = { - globalUser: runtime.storage.projects.getGlobalGitUser() - } + + safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); + runtime.settings.exportNodeSettings(safeSettings); } - safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); - - runtime.settings.exportNodeSettings(safeSettings); resolve(safeSettings); }catch(err) { diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index d812c05f2..dbcb5fb61 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -51,6 +51,8 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_auth_failed"; } else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_auth_failed"; + } else if(/Authentication failed/i.test(stderr)) { + err.code = "git_auth_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) { diff --git a/test/unit/@node-red/editor-api/lib/admin/index_spec.js b/test/unit/@node-red/editor-api/lib/admin/index_spec.js index ead5f274a..4f9f2d55a 100644 --- a/test/unit/@node-red/editor-api/lib/admin/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/index_spec.js @@ -102,7 +102,7 @@ describe("api/admin/index", function() { }); before(function() { - app = adminApi.init({}); + app = adminApi.init({},{}); }); beforeEach(function() { diff --git a/test/unit/@node-red/editor-api/lib/admin/settings_spec.js b/test/unit/@node-red/editor-api/lib/admin/settings_spec.js new file mode 100644 index 000000000..d0c4cbe94 --- /dev/null +++ b/test/unit/@node-red/editor-api/lib/admin/settings_spec.js @@ -0,0 +1,93 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ + +var should = require("should"); +var request = require('supertest'); +var express = require('express'); +var bodyParser = require("body-parser"); +var sinon = require('sinon'); + +var app; + +var NR_TEST_UTILS = require("nr-test-utils"); + +var info = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/settings"); +var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"); + +describe("api/editor/settings", function() { + before(function() { + sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); + app = express(); + app.use(bodyParser.json()); + app.get("/settings",info.runtimeSettings); + }); + + after(function() { + theme.settings.restore(); + }); + + it('returns the runtime settings', function(done) { + info.init({},{ + settings: { + getRuntimeSettings: function(opts) { + return Promise.resolve({ + a:1, + b:2, + editorTheme: { existing: 789 } + }) + } + } + }); + request(app) + .get("/settings") + .expect(200) + .end(function(err,res) { + if (err) { + return done(err); + } + res.body.should.have.property("a",1); + res.body.should.have.property("b",2); + res.body.should.have.property("editorTheme",{existing: 789, test:456}); + done(); + }); + }); + it('returns the runtime settings - disableEditor true', function(done) { + info.init({disableEditor: true},{ + settings: { + getRuntimeSettings: function(opts) { + return Promise.resolve({ + a:1, + b:2 + }) + } + } + }); + request(app) + .get("/settings") + .expect(200) + .end(function(err,res) { + if (err) { + return done(err); + } + res.body.should.have.property("a",1); + res.body.should.have.property("b",2); + // no editorTheme if disabledEditor true + res.body.should.not.have.property("editorTheme"); + done(); + }); + }); + +}); diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 228163684..18a179ac7 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -155,6 +155,10 @@ describe("api/auth/users", function() { }); describe('#get',function() { + it("returns null for tokenHeader", function() { + should.not.exist(Users.tokenHeader()); + }); + it('delegates get user',function(done) { Users.get('dave').then(function(user) { try { diff --git a/test/unit/@node-red/editor-api/lib/editor/settings_spec.js b/test/unit/@node-red/editor-api/lib/editor/settings_spec.js index 8fcdbeba2..f8c690751 100644 --- a/test/unit/@node-red/editor-api/lib/editor/settings_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/settings_spec.js @@ -32,7 +32,6 @@ describe("api/editor/settings", function() { sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); app = express(); app.use(bodyParser.json()); - app.get("/settings",info.runtimeSettings); app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings); app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings); }); @@ -41,31 +40,6 @@ describe("api/editor/settings", function() { theme.settings.restore(); }); - it('returns the runtime settings', function(done) { - info.init({ - settings: { - getRuntimeSettings: function(opts) { - return Promise.resolve({ - a:1, - b:2, - editorTheme: { existing: 789 } - }) - } - } - }); - request(app) - .get("/settings") - .expect(200) - .end(function(err,res) { - if (err) { - return done(err); - } - res.body.should.have.property("a",1); - res.body.should.have.property("b",2); - res.body.should.have.property("editorTheme",{existing: 789, test:456}); - done(); - }); - }); it('returns the user settings', function(done) { info.init({ settings: { diff --git a/test/unit/@node-red/runtime/lib/api/settings_spec.js b/test/unit/@node-red/runtime/lib/api/settings_spec.js index dbc5567da..3830e9ab2 100644 --- a/test/unit/@node-red/runtime/lib/api/settings_spec.js +++ b/test/unit/@node-red/runtime/lib/api/settings_spec.js @@ -101,7 +101,58 @@ describe("runtime-api/settings", function() { result.user.should.not.have.property("private"); }) }); + it("gets the filtered settings when editor disabled ", function() { + settings.init({ + settings: { + disableEditor: true, + foo: 123, + httpNodeRoot: "testHttpNodeRoot", + version: "testVersion", + paletteCategories :["red","blue","green"], + exportNodeSettings: (obj) => { + obj.testNodeSetting = "helloWorld"; + } + }, + nodes: { + listContextStores: () => { return {stores:["file","memory"], default: "file"} }, + paletteEditorEnabled: () => false, + getCredentialKeyType: () => "test-key-type" + }, + storage: { + projects: { + getActiveProject: () => 'test-active-project', + getFlowFilename: () => 'test-flow-file', + getCredentialsFilename: () => 'test-creds-file', + getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} + } + } + }) + return settings.getRuntimeSettings({ + user: { + username: "nick", + anonymous: false, + image: "http://example.com", + permissions: "*", + private: "secret" + } + }).then(result => { + result.should.have.property("user"); + result.user.should.have.property("username","nick"); + result.user.should.have.property("permissions","*"); + result.user.should.have.property("image","http://example.com"); + result.user.should.have.property("anonymous",false); + result.user.should.not.have.property("private"); + // Filtered out when disableEditor is true + result.should.not.have.property("paletteCategories",["red","blue","green"]); + result.should.not.have.property("testNodeSetting","helloWorld"); + result.should.not.have.property("foo",123); + result.should.not.have.property("flowEncryptionType","test-key-type"); + result.should.not.have.property("project"); + result.should.not.have.property("git"); + + }) + }); it('includes project settings if projects available', function() { settings.init({ settings: {