From 50e2dcbcd5530ff9b5a65b6c834b429d4a2b78f8 Mon Sep 17 00:00:00 2001 From: Hideki Nakamura Date: Thu, 5 Jul 2018 18:58:02 -0700 Subject: [PATCH 01/38] Add a processing to check specified API Access Tokens --- red/api/auth/api-access-tokens.js | 36 +++++++++++++++++++++++++++++++ red/api/auth/index.js | 2 ++ red/api/auth/strategies.js | 25 +++++++++++++-------- 3 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 red/api/auth/api-access-tokens.js diff --git a/red/api/auth/api-access-tokens.js b/red/api/auth/api-access-tokens.js new file mode 100644 index 000000000..bf61516b3 --- /dev/null +++ b/red/api/auth/api-access-tokens.js @@ -0,0 +1,36 @@ +/** + * 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 generatedTokens; + +module.exports = { + init: function(apiAccessTokensSettings) { + generatedTokens = {}; + if ( Array.isArray(apiAccessTokensSettings) ) { + generatedTokens = apiAccessTokensSettings.reduce(function(prev, current) { + prev[current.token] = { + username: current.username, + scope: current.permissions + }; + return prev; + }, {}); + } + return Promise.resolve(); + }, + get: function(token) { + var info = generatedTokens[token] || null; + return Promise.resolve(info); + } +} diff --git a/red/api/auth/index.js b/red/api/auth/index.js index 211a712a7..3e3a28bfa 100644 --- a/red/api/auth/index.js +++ b/red/api/auth/index.js @@ -19,6 +19,7 @@ var oauth2orize = require("oauth2orize"); var strategies = require("./strategies"); var Tokens = require("./tokens"); +var apiAccessTokens = require("./api-access-tokens"); var Users = require("./users"); var permissions = require("./permissions"); @@ -42,6 +43,7 @@ function init(runtime) { if (settings.adminAuth) { Users.init(settings.adminAuth); Tokens.init(settings.adminAuth,runtime.storage); + apiAccessTokens.init(settings.apiAccessTokens); strategies.init(runtime); } } diff --git a/red/api/auth/strategies.js b/red/api/auth/strategies.js index 0f5b554e4..5d3124497 100644 --- a/red/api/auth/strategies.js +++ b/red/api/auth/strategies.js @@ -22,6 +22,7 @@ var crypto = require("crypto"); var util = require("util"); var Tokens = require("./tokens"); +var apiAccessTokens = require("./api-access-tokens"); var Users = require("./users"); var Clients = require("./clients"); var permissions = require("./permissions"); @@ -30,21 +31,27 @@ var log; var bearerStrategy = function (accessToken, done) { // is this a valid token? - Tokens.get(accessToken).then(function(token) { - if (token) { - Users.get(token.user).then(function(user) { - if (user) { - done(null,user,{scope:token.scope}); + apiAccessTokens.get(accessToken).then(function(tokenInfo) { + if (tokenInfo && tokenInfo.username && tokenInfo.scope) { + done(null, tokenInfo.username,{scope:tokenInfo.scope}); + } else { + Tokens.get(accessToken).then(function(token) { + if (token) { + Users.get(token.user).then(function(user) { + if (user) { + done(null,user,{scope:token.scope}); + } else { + log.audit({event: "auth.invalid-token"}); + done(null,false); + } + }); } else { log.audit({event: "auth.invalid-token"}); done(null,false); } }); - } else { - log.audit({event: "auth.invalid-token"}); - done(null,false); } - }); + }) } bearerStrategy.BearerStrategy = new BearerStrategy(bearerStrategy); From b14a0e0dde4c2892acd9a5e557fdea523e6d8064 Mon Sep 17 00:00:00 2001 From: Hideki Nakamura Date: Tue, 10 Jul 2018 17:25:01 -0700 Subject: [PATCH 02/38] Merge the logic for api access token to tokens.js so as not to change strategies.js --- red/api/auth/api-access-tokens.js | 36 ------------------------------- red/api/auth/index.js | 6 ++---- red/api/auth/strategies.js | 25 ++++++++------------- red/api/auth/tokens.js | 29 ++++++++++++++++++++----- red/api/auth/users.js | 23 +++++++++++++++++--- 5 files changed, 55 insertions(+), 64 deletions(-) delete mode 100644 red/api/auth/api-access-tokens.js diff --git a/red/api/auth/api-access-tokens.js b/red/api/auth/api-access-tokens.js deleted file mode 100644 index bf61516b3..000000000 --- a/red/api/auth/api-access-tokens.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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 generatedTokens; - -module.exports = { - init: function(apiAccessTokensSettings) { - generatedTokens = {}; - if ( Array.isArray(apiAccessTokensSettings) ) { - generatedTokens = apiAccessTokensSettings.reduce(function(prev, current) { - prev[current.token] = { - username: current.username, - scope: current.permissions - }; - return prev; - }, {}); - } - return Promise.resolve(); - }, - get: function(token) { - var info = generatedTokens[token] || null; - return Promise.resolve(info); - } -} diff --git a/red/api/auth/index.js b/red/api/auth/index.js index 3e3a28bfa..c89c8d240 100644 --- a/red/api/auth/index.js +++ b/red/api/auth/index.js @@ -19,7 +19,6 @@ var oauth2orize = require("oauth2orize"); var strategies = require("./strategies"); var Tokens = require("./tokens"); -var apiAccessTokens = require("./api-access-tokens"); var Users = require("./users"); var permissions = require("./permissions"); @@ -41,9 +40,8 @@ function init(runtime) { settings = runtime.settings; log = runtime.log; if (settings.adminAuth) { - Users.init(settings.adminAuth); - Tokens.init(settings.adminAuth,runtime.storage); - apiAccessTokens.init(settings.apiAccessTokens); + Users.init(settings.adminAuth,settings.apiAccessTokens); + Tokens.init(settings.adminAuth,runtime.storage,settings.apiAccessTokens); strategies.init(runtime); } } diff --git a/red/api/auth/strategies.js b/red/api/auth/strategies.js index 5d3124497..0f5b554e4 100644 --- a/red/api/auth/strategies.js +++ b/red/api/auth/strategies.js @@ -22,7 +22,6 @@ var crypto = require("crypto"); var util = require("util"); var Tokens = require("./tokens"); -var apiAccessTokens = require("./api-access-tokens"); var Users = require("./users"); var Clients = require("./clients"); var permissions = require("./permissions"); @@ -31,27 +30,21 @@ var log; var bearerStrategy = function (accessToken, done) { // is this a valid token? - apiAccessTokens.get(accessToken).then(function(tokenInfo) { - if (tokenInfo && tokenInfo.username && tokenInfo.scope) { - done(null, tokenInfo.username,{scope:tokenInfo.scope}); - } else { - Tokens.get(accessToken).then(function(token) { - if (token) { - Users.get(token.user).then(function(user) { - if (user) { - done(null,user,{scope:token.scope}); - } else { - log.audit({event: "auth.invalid-token"}); - done(null,false); - } - }); + Tokens.get(accessToken).then(function(token) { + if (token) { + Users.get(token.user).then(function(user) { + if (user) { + done(null,user,{scope:token.scope}); } else { log.audit({event: "auth.invalid-token"}); done(null,false); } }); + } else { + log.audit({event: "auth.invalid-token"}); + done(null,false); } - }) + }); } bearerStrategy.BearerStrategy = new BearerStrategy(bearerStrategy); diff --git a/red/api/auth/tokens.js b/red/api/auth/tokens.js index 620cd6a12..054234215 100644 --- a/red/api/auth/tokens.js +++ b/red/api/auth/tokens.js @@ -32,6 +32,8 @@ var sessions = {}; var loadedSessions = null; +var apiAccessTokens; + function expireSessions() { var now = Date.now(); var modified = false; @@ -61,22 +63,39 @@ function loadSessions() { } module.exports = { - init: function(adminAuthSettings, _storage) { + init: function(adminAuthSettings, _storage, apiAccessTokensSettings) { storage = _storage; sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds // At this point, storage will not have been initialised, so defer loading // the sessions until there's a request for them. loadedSessions = null; + + apiAccessTokens = {}; + if ( Array.isArray(apiAccessTokensSettings) ) { + apiAccessTokens = apiAccessTokensSettings.reduce(function(prev, current) { + prev[current.token] = { + user: current.username, + scope: current.permissions + }; + return prev; + }, {}); + } return Promise.resolve(); }, get: function(token) { return loadSessions().then(function() { - if (sessions[token]) { - if (sessions[token].expires < Date.now()) { - return expireSessions().then(function() { return null }); + var info = apiAccessTokens[token] || null; + + if (info) { + return Promise.resolve(info); + } else { + if (sessions[token]) { + if (sessions[token].expires < Date.now()) { + return expireSessions().then(function() { return null }); + } } + return Promise.resolve(sessions[token]); } - return Promise.resolve(sessions[token]); }); }, create: function(user,client,scope) { diff --git a/red/api/auth/users.js b/red/api/auth/users.js index 24a762958..4c2aaaf09 100644 --- a/red/api/auth/users.js +++ b/red/api/auth/users.js @@ -57,18 +57,35 @@ function getDefaultUser() { } var api = { - get: get, + get: wrapperGetUserFromSettings(get), authenticate: authenticate, default: getDefaultUser } -function init(config) { +var apiAccessUsers = {}; +function wrapperGetUserFromSettings (getFunc) { + return function (username) { + if (apiAccessUsers[username]) { + return Promise.resolve(apiAccessUsers[username]); + } else { + return getFunc(username); + } + }; +} + +function init(config, apiAccessTokensSettings) { users = {}; defaultUser = null; + apiAccessUsers = apiAccessTokensSettings.reduce(function (prev, current) { + if (current.username) { + prev[current.username] = current.username; + } + return prev; + }, {}); if (config.type == "credentials" || config.type == "strategy") { if (config.users) { if (typeof config.users === "function") { - api.get = config.users; + api.get = wrapperGetUserFromSettings(config.users); } else { var us = config.users; /* istanbul ignore else */ From cb0e631b853ac68ac8814a3db31caf4bc87b0e1d Mon Sep 17 00:00:00 2001 From: Hideki Nakamura Date: Tue, 11 Sep 2018 09:44:18 -0700 Subject: [PATCH 03/38] Update the implementation according to the Design notes --- red/api/auth/index.js | 20 +++++++++++--------- red/api/auth/tokens.js | 10 +++++----- red/api/auth/users.js | 23 +++-------------------- settings.js | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/red/api/auth/index.js b/red/api/auth/index.js index c89c8d240..ae7f55818 100644 --- a/red/api/auth/index.js +++ b/red/api/auth/index.js @@ -40,8 +40,9 @@ function init(runtime) { settings = runtime.settings; log = runtime.log; if (settings.adminAuth) { - Users.init(settings.adminAuth,settings.apiAccessTokens); - Tokens.init(settings.adminAuth,runtime.storage,settings.apiAccessTokens); + var mergedAdminAuth = Object.assign(settings.adminAuth, settings.adminAuth.module); + Users.init(mergedAdminAuth); + Tokens.init(mergedAdminAuth,runtime.storage); strategies.init(runtime); } } @@ -81,23 +82,24 @@ function getToken(req,res,next) { function login(req,res) { var response = {}; if (settings.adminAuth) { - if (settings.adminAuth.type === "credentials") { + var mergedAdminAuth = Object.assign(settings.adminAuth, settings.adminAuth.module); + if (mergedAdminAuth.type === "credentials") { response = { "type":"credentials", "prompts":[{id:"username",type:"text",label:"user.username"},{id:"password",type:"password",label:"user.password"}] } - } else if (settings.adminAuth.type === "strategy") { + } else if (mergedAdminAuth.type === "strategy") { var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot; response = { "type":"strategy", - "prompts":[{type:"button",label:settings.adminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] + "prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] } - if (settings.adminAuth.strategy.icon) { - response.prompts[0].icon = settings.adminAuth.strategy.icon; + if (mergedAdminAuth.strategy.icon) { + response.prompts[0].icon = mergedAdminAuth.strategy.icon; } - if (settings.adminAuth.strategy.image) { - response.prompts[0].image = theme.serveFile('/login/',settings.adminAuth.strategy.image); + if (mergedAdminAuth.strategy.image) { + response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image); } } if (theme.context().login && theme.context().login.image) { diff --git a/red/api/auth/tokens.js b/red/api/auth/tokens.js index 054234215..daa058787 100644 --- a/red/api/auth/tokens.js +++ b/red/api/auth/tokens.js @@ -63,7 +63,7 @@ function loadSessions() { } module.exports = { - init: function(adminAuthSettings, _storage, apiAccessTokensSettings) { + init: function(adminAuthSettings, _storage) { storage = _storage; sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds // At this point, storage will not have been initialised, so defer loading @@ -71,11 +71,11 @@ module.exports = { loadedSessions = null; apiAccessTokens = {}; - if ( Array.isArray(apiAccessTokensSettings) ) { - apiAccessTokens = apiAccessTokensSettings.reduce(function(prev, current) { + if ( Array.isArray(adminAuthSettings.tokens) ) { + apiAccessTokens = adminAuthSettings.tokens.reduce(function(prev, current) { prev[current.token] = { - user: current.username, - scope: current.permissions + user: current.user, + scope: current.scope }; return prev; }, {}); diff --git a/red/api/auth/users.js b/red/api/auth/users.js index 4c2aaaf09..24a762958 100644 --- a/red/api/auth/users.js +++ b/red/api/auth/users.js @@ -57,35 +57,18 @@ function getDefaultUser() { } var api = { - get: wrapperGetUserFromSettings(get), + get: get, authenticate: authenticate, default: getDefaultUser } -var apiAccessUsers = {}; -function wrapperGetUserFromSettings (getFunc) { - return function (username) { - if (apiAccessUsers[username]) { - return Promise.resolve(apiAccessUsers[username]); - } else { - return getFunc(username); - } - }; -} - -function init(config, apiAccessTokensSettings) { +function init(config) { users = {}; defaultUser = null; - apiAccessUsers = apiAccessTokensSettings.reduce(function (prev, current) { - if (current.username) { - prev[current.username] = current.username; - } - return prev; - }, {}); if (config.type == "credentials" || config.type == "strategy") { if (config.users) { if (typeof config.users === "function") { - api.get = wrapperGetUserFromSettings(config.users); + api.get = config.users; } else { var us = config.users; /* istanbul ignore else */ diff --git a/settings.js b/settings.js index dc79223fe..90151d32a 100644 --- a/settings.js +++ b/settings.js @@ -124,6 +124,25 @@ module.exports = { // }] //}, + // If you would like to use not only this access token feature but also a Node-RED + // plugin module for authenticatiing users such as node-red-auth-github, + // you can define the module in adminAuth.module property. + //adminAuth: { + // module: require('node-red-auth-github')({ + // clientID: GITHUB_CLIENT_ID, + // clientSecret: GITHUB_CLIENT_SECRET, + // baseURL: "http://localhost:1880/", + // users: [ + // { username: "knolleary",permissions: ["*"]} + // ] + // }) + // tokens: [{ + // token: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", + // user: "knolleary", + // scope: ["*"] + // }] + //}, + // To password protect the node-defined HTTP endpoints (httpNodeRoot), or // the static content (httpStatic), the following properties can be used. // The pass field is a bcrypt hash of the password. From c102828a998fd4513cc3cf3aba6cdfa5cae00a0a Mon Sep 17 00:00:00 2001 From: Hideki Nakamura Date: Tue, 11 Sep 2018 10:36:54 -0700 Subject: [PATCH 04/38] Add some test cases --- test/red/api/auth/index_spec.js | 168 +++++++++++++++++++++++++++++++ test/red/api/auth/tokens_spec.js | 23 +++++ 2 files changed, 191 insertions(+) diff --git a/test/red/api/auth/index_spec.js b/test/red/api/auth/index_spec.js index 9849b602c..a27cf7c29 100644 --- a/test/red/api/auth/index_spec.js +++ b/test/red/api/auth/index_spec.js @@ -114,6 +114,174 @@ describe("api/auth/index",function() { }}); }); + it("returns login details - strategy - merged a adminAuth.module object to a adminAuth object", function(done) { + auth.init({ + settings: { + adminAuth: { + type: "strategy", + strategy:{ + label:"test-strategy", + icon:"test-icon" + }, + tokens: [{ + token: "test-token", + user: "test-user", + scope: ["*"] + }] + } + }, + log:{audit:function(){}} + }); + auth.login(null,{json: function(resp) { + resp.should.have.a.property("type","strategy"); + resp.should.have.a.property("prompts"); + resp.prompts.should.have.a.lengthOf(1); + resp.prompts[0].should.have.a.property("type","button"); + resp.prompts[0].should.have.a.property("label","test-strategy"); + resp.prompts[0].should.have.a.property("icon","test-icon"); + + done(); + }}); + }); + + }); + + describe("init", function() { + var spyTokensInit; + var spyUsersInit; + beforeEach(function() { + spyTokensInit = sinon.spy(Tokens,"init"); + spyUsersInit = sinon.spy(Users,"init"); + }); + this.afterEach(function() { + spyTokensInit.restore(); + spyUsersInit.restore(); + }); + it("merges an adminAuth object and an adminAuth.module object - module object is null", function(done) { + auth.init({ + settings: { + adminAuth: { + type: "strategy", + strategy:{ + label:"test-strategy", + icon:"test-icon" + }, + tokens: [{ + token: "test-token", + user: "test-user", + scope: ["*"] + }] + } + }, + log:{audit:function(){}} + }); + spyTokensInit.args[0][0].should.have.a.property("type","strategy"); + spyTokensInit.args[0][0].should.have.a.property("strategy"); + spyTokensInit.args[0][0].strategy.should.have.a.property("label","test-strategy"); + spyTokensInit.args[0][0].strategy.should.have.a.property("icon","test-icon"); + spyTokensInit.args[0][0].should.have.a.property("tokens"); + spyTokensInit.args[0][0].tokens.should.have.a.lengthOf(1); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("token","test-token"); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("user","test-user"); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("scope"); + spyTokensInit.args[0][0].tokens[0].scope.should.have.a.lengthOf(1); + spyUsersInit.args[0][0].should.have.a.property("type","strategy"); + spyUsersInit.args[0][0].should.have.a.property("strategy"); + spyUsersInit.args[0][0].strategy.should.have.a.property("label","test-strategy"); + spyUsersInit.args[0][0].strategy.should.have.a.property("icon","test-icon"); + spyUsersInit.args[0][0].should.have.a.property("tokens"); + spyUsersInit.args[0][0].tokens.should.have.a.lengthOf(1); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("token","test-token"); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("user","test-user"); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("scope"); + spyUsersInit.args[0][0].tokens[0].scope.should.have.a.lengthOf(1); + done(); + }); + it("merges an adminAuth object and an adminAuth.module object - not conflict", function(done) { + auth.init({ + settings: { + adminAuth: { + module: { + type: "strategy", + strategy:{ + label:"test-strategy", + icon:"test-icon" + } + }, + tokens: [{ + token: "test-token", + user: "test-user", + scope: ["*"] + }] + } + }, + log:{audit:function(){}} + }); + spyTokensInit.args[0][0].should.have.a.property("type","strategy"); + spyTokensInit.args[0][0].should.have.a.property("strategy"); + spyTokensInit.args[0][0].strategy.should.have.a.property("label","test-strategy"); + spyTokensInit.args[0][0].strategy.should.have.a.property("icon","test-icon"); + spyTokensInit.args[0][0].should.have.a.property("tokens"); + spyTokensInit.args[0][0].tokens.should.have.a.lengthOf(1); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("token","test-token"); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("user","test-user"); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("scope"); + spyTokensInit.args[0][0].tokens[0].scope.should.have.a.lengthOf(1); + spyUsersInit.args[0][0].should.have.a.property("type","strategy"); + spyUsersInit.args[0][0].should.have.a.property("strategy"); + spyUsersInit.args[0][0].strategy.should.have.a.property("label","test-strategy"); + spyUsersInit.args[0][0].strategy.should.have.a.property("icon","test-icon"); + spyUsersInit.args[0][0].should.have.a.property("tokens"); + spyUsersInit.args[0][0].tokens.should.have.a.lengthOf(1); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("token","test-token"); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("user","test-user"); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("scope"); + spyUsersInit.args[0][0].tokens[0].scope.should.have.a.lengthOf(1); + done(); + }); + it("merges an adminAuth object and an adminAuth.module object - conflict", function(done) { + auth.init({ + settings: { + adminAuth: { + module: { + type: "strategy", + strategy:{ + label:"test-strategy", + icon:"test-icon" + } + }, + type: "credentials", + tokens: [{ + token: "test-token", + user: "test-user", + scope: ["*"] + }] + } + }, + log:{audit:function(){}} + }); + spyTokensInit.args[0][0].should.have.a.property("type","strategy"); + spyTokensInit.args[0][0].should.have.a.property("strategy"); + spyTokensInit.args[0][0].strategy.should.have.a.property("label","test-strategy"); + spyTokensInit.args[0][0].strategy.should.have.a.property("icon","test-icon"); + spyTokensInit.args[0][0].should.have.a.property("tokens"); + spyTokensInit.args[0][0].tokens.should.have.a.lengthOf(1); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("token","test-token"); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("user","test-user"); + spyTokensInit.args[0][0].tokens[0].should.have.a.property("scope"); + spyTokensInit.args[0][0].tokens[0].scope.should.have.a.lengthOf(1); + spyUsersInit.args[0][0].should.have.a.property("type","strategy"); + spyUsersInit.args[0][0].should.have.a.property("strategy"); + spyUsersInit.args[0][0].strategy.should.have.a.property("label","test-strategy"); + spyUsersInit.args[0][0].strategy.should.have.a.property("icon","test-icon"); + spyUsersInit.args[0][0].should.have.a.property("tokens"); + spyUsersInit.args[0][0].tokens.should.have.a.lengthOf(1); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("token","test-token"); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("user","test-user"); + spyUsersInit.args[0][0].tokens[0].should.have.a.property("scope"); + spyUsersInit.args[0][0].tokens[0].scope.should.have.a.lengthOf(1); + done(); + }); }); }); diff --git a/test/red/api/auth/tokens_spec.js b/test/red/api/auth/tokens_spec.js index a9c5a7831..fa17391c4 100644 --- a/test/red/api/auth/tokens_spec.js +++ b/test/red/api/auth/tokens_spec.js @@ -92,6 +92,29 @@ describe("api/auth/tokens", function() { }); }); }); + + it('returns a valid api token', function(done) { + Tokens.init({ + tokens: [{ + token: "1234", + user: "fred", + }] + },{ + getSessions:function() { + return when.resolve({}); + } + }).then(function() { + Tokens.get("1234").then(function(token) { + try { + token.should.have.a.property("user","fred"); + done(); + } catch(err) { + done(err); + } + }); + }); + + }); }); describe("#create",function() { From 716aa740049b6b9ad3d7d4924d18f756b98f9918 Mon Sep 17 00:00:00 2001 From: Hideki Nakamura Date: Mon, 17 Sep 2018 10:18:36 -0700 Subject: [PATCH 05/38] Fix code not to change settings.adminAuth --- red/api/auth/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/red/api/auth/index.js b/red/api/auth/index.js index ae7f55818..1a82ad1eb 100644 --- a/red/api/auth/index.js +++ b/red/api/auth/index.js @@ -40,7 +40,7 @@ function init(runtime) { settings = runtime.settings; log = runtime.log; if (settings.adminAuth) { - var mergedAdminAuth = Object.assign(settings.adminAuth, settings.adminAuth.module); + var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module); Users.init(mergedAdminAuth); Tokens.init(mergedAdminAuth,runtime.storage); strategies.init(runtime); @@ -82,7 +82,7 @@ function getToken(req,res,next) { function login(req,res) { var response = {}; if (settings.adminAuth) { - var mergedAdminAuth = Object.assign(settings.adminAuth, settings.adminAuth.module); + var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module); if (mergedAdminAuth.type === "credentials") { response = { "type":"credentials", From 1a226c4dc6a6da2ee91ee3d6fbedbb8d98110ada Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 21 Sep 2018 21:07:44 +0900 Subject: [PATCH 06/38] fix multiple input message processing of file node --- nodes/core/storage/50-file.js | 32 ++++++++++++++++-- test/nodes/core/storage/50-file_spec.js | 43 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 76cedb136..abe591c9d 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -29,8 +29,9 @@ module.exports = function(RED) { var node = this; node.wstream = null; node.data = []; + node.msgQueue = []; - this.on("input",function(msg) { + function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; if ((!node.filename) && (!node.tout)) { node.tout = setTimeout(function() { @@ -41,6 +42,7 @@ module.exports = function(RED) { } if (filename === "") { node.warn(RED._("file.errors.nofilename")); + done(); } else if (node.overwriteFile === "delete") { fs.unlink(filename, function (err) { if (err) { @@ -51,6 +53,7 @@ module.exports = function(RED) { } node.send(msg); } + done(); }); } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) { var dir = path.dirname(filename); @@ -59,6 +62,7 @@ module.exports = function(RED) { fs.ensureDirSync(dir); } catch(err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); + done(); return; } } @@ -82,6 +86,7 @@ module.exports = function(RED) { node.wstream.on("open", function() { node.wstream.end(packet.data, function() { node.send(packet.msg); + done(); }); }) })(node.data.shift()); @@ -130,6 +135,7 @@ module.exports = function(RED) { var packet = node.data.shift() node.wstream.write(packet.data, function() { node.send(packet.msg); + done(); }); } else { @@ -137,14 +143,34 @@ module.exports = function(RED) { var packet = node.data.shift() node.wstream.end(packet.data, function() { node.send(packet.msg); + delete node.wstream; + delete node.wstreamIno; + done(); }); - delete node.wstream; - delete node.wstreamIno; } } } } + } + + this.on("input", function(msg) { + var msgQueue = node.msgQueue; + function processQ() { + var msg = msgQueue[0]; + processMsg(msg, function() { + msgQueue.shift(); + if (msgQueue.length > 0) { + processQ(); + } + }); + } + if (msgQueue.push(msg) > 1) { + // pending write exists + return; + } + processQ(); }); + this.on('close', function() { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index e61c3fb68..cba22cd54 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -540,6 +540,49 @@ describe('file Nodes', function() { }); }); + it('should write to multiple files', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, "createDir":true, wires: [["helperNode1"]]}, + {id:"helperNode1", type:"helper"}]; + var tmp_path = path.join(resourcesDir, "tmp"); + var len = 1024*1024*10; + var file_count = 5; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + var n2 = helper.getNode("helperNode1"); + var count = 0; + n2.on("input", function(msg) { + try { + count++; + if (count == file_count) { + for(var i = 0; i < file_count; i++) { + var name = path.join(tmp_path, String(i)); + var f = fs.readFileSync(name); + f.should.have.length(len); + f[0].should.have.equal(i); + } + fs.removeSync(tmp_path); + done(); + } + } + catch (e) { + try { + fs.removeSync(tmp_path); + } + catch (e1) { + } + done(e); + } + }); + for(var i = 0; i < file_count; i++) { + var data = new Buffer(len); + data.fill(i); + var name = path.join(tmp_path, String(i)); + var msg = {payload:data, filename:name}; + n1.receive(msg); + } + }); + }); + }); From 61681bb1d613e1913986cdc591c7816fcfc600d9 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 21 Sep 2018 23:02:45 +0900 Subject: [PATCH 07/38] lift processQ function --- nodes/core/storage/50-file.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index abe591c9d..70d9984af 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -153,22 +153,23 @@ module.exports = function(RED) { } } + function processQ(queue) { + var msg = queue[0]; + processMsg(msg, function() { + queue.shift(); + if (queue.length > 0) { + processQ(queue); + } + }); + } + this.on("input", function(msg) { var msgQueue = node.msgQueue; - function processQ() { - var msg = msgQueue[0]; - processMsg(msg, function() { - msgQueue.shift(); - if (msgQueue.length > 0) { - processQ(); - } - }); - } if (msgQueue.push(msg) > 1) { // pending write exists return; } - processQ(); + processQ(msgQueue); }); this.on('close', function() { From a345089c8b5c8c0016952150523bfbfb55bee5cf Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Wed, 26 Sep 2018 12:39:12 +0900 Subject: [PATCH 08/38] wait closing while penging messages exist --- nodes/core/storage/50-file.js | 22 ++- test/nodes/core/storage/50-file_spec.js | 202 +++++++++++++++--------- 2 files changed, 144 insertions(+), 80 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 70d9984af..853f93186 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -30,6 +30,7 @@ module.exports = function(RED) { node.wstream = null; node.data = []; node.msgQueue = []; + node.closing = false; function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; @@ -170,12 +171,31 @@ module.exports = function(RED) { return; } processQ(msgQueue); + if (node.closing) { + closeNode(); + } }); - this.on('close', function() { + function closeNode() { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } node.status({}); + node.closing = false; + } + + this.on('close', function() { + if (node.closing) { + // already closing + return; + } + node.closing = true; + if (node.msgQueue.length > 0) { + // close after queue processed + return; + } + else { + closeNode(); + } }); } RED.nodes.registerType("file",FileNode); diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index cba22cd54..b60933649 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -46,14 +46,14 @@ describe('file Nodes', function() { it('should be loaded', function(done) { var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":true}]; helper.load(fileNode, flow, function() { - try { + try { var fileNode1 = helper.getNode("fileNode1"); fileNode1.should.have.property('name', 'fileNode'); done(); - } - catch (e) { - done(e); - } + } + catch (e) { + done(e); + } }); }); @@ -64,16 +64,16 @@ describe('file Nodes', function() { var n1 = helper.getNode("fileNode1"); var n2 = helper.getNode("helperNode1"); n2.on("input", function(msg) { - try { - var f = fs.readFileSync(fileToTest); - f.should.have.length(4); - fs.unlinkSync(fileToTest); - msg.should.have.property("payload", "test"); - done(); - } - catch (e) { - done(e); - } + try { + var f = fs.readFileSync(fileToTest); + f.should.have.length(4); + fs.unlinkSync(fileToTest); + msg.should.have.property("payload", "test"); + done(); + } + catch (e) { + done(e); + } }); n1.receive({payload:"test"}); }); @@ -93,26 +93,26 @@ describe('file Nodes', function() { var data = ["test2", true, 999, [2]]; n2.on("input", function (msg) { - try { - msg.should.have.property("payload"); - data.should.containDeep([msg.payload]); - if (count === 3) { + try { + msg.should.have.property("payload"); + data.should.containDeep([msg.payload]); + if (count === 3) { var f = fs.readFileSync(fileToTest).toString(); if (os.type() !== "Windows_NT") { - f.should.have.length(19); - f.should.equal("test2\ntrue\n999\n[2]\n"); + f.should.have.length(19); + f.should.equal("test2\ntrue\n999\n[2]\n"); } else { - f.should.have.length(23); - f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n"); + f.should.have.length(23); + f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n"); } done(); - } - count++; - } - catch (e) { - done(e); - } + } + count++; + } + catch (e) { + done(e); + } }); n1.receive({payload:"test2"}); // string @@ -142,37 +142,37 @@ describe('file Nodes', function() { var count = 0; n2.on("input", function (msg) { - try { - msg.should.have.property("payload"); - data.should.containDeep([msg.payload]); - try { + try { + msg.should.have.property("payload"); + data.should.containDeep([msg.payload]); + try { if (count === 1) { - // Check they got appended as expected - var f = fs.readFileSync(fileToTest).toString(); - f.should.equal("onetwo"); + // Check they got appended as expected + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("onetwo"); - // Delete the file - fs.unlinkSync(fileToTest); - setTimeout(function() { + // Delete the file + fs.unlinkSync(fileToTest); + setTimeout(function() { // Send two more messages to the file n1.receive({payload:"three"}); n1.receive({payload:"four"}); - }, wait); + }, wait); } if (count === 3) { - var f = fs.readFileSync(fileToTest).toString(); - f.should.equal("threefour"); - fs.unlinkSync(fileToTest); - done(); + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("threefour"); + fs.unlinkSync(fileToTest); + done(); } - } catch(err) { + } catch(err) { done(err); - } - count++; - } - catch (e) { - done(e); - } + } + count++; + } + catch (e) { + done(e); + } }); // Send two messages to the file @@ -197,7 +197,7 @@ describe('file Nodes', function() { n2.on("input", function (msg) { try { msg.should.have.property("payload"); - data.should.containDeep([msg.payload]); + data.should.containDeep([msg.payload]); if (count == 1) { // Check they got appended as expected var f = fs.readFileSync(fileToTest).toString(); @@ -256,25 +256,25 @@ describe('file Nodes', function() { var n2 = helper.getNode("helperNode1"); n2.on("input", function (msg) { - try { - msg.should.have.property("payload", "fine"); - msg.should.have.property("filename", fileToTest); + try { + msg.should.have.property("payload", "fine"); + msg.should.have.property("filename", fileToTest); - var f = fs.readFileSync(fileToTest).toString(); - if (os.type() !== "Windows_NT") { + var f = fs.readFileSync(fileToTest).toString(); + if (os.type() !== "Windows_NT") { f.should.have.length(5); f.should.equal("fine\n"); - } - else { + } + else { f.should.have.length(6); f.should.equal("fine\r\n"); - } - done(); - } - catch (e) { - done(e); - } - }); + } + done(); + } + catch (e) { + done(e); + } + }); n1.receive({payload:"fine", filename:fileToTest}); }); @@ -551,27 +551,27 @@ describe('file Nodes', function() { var n2 = helper.getNode("helperNode1"); var count = 0; n2.on("input", function(msg) { - try { + try { count++; if (count == file_count) { for(var i = 0; i < file_count; i++) { var name = path.join(tmp_path, String(i)); - var f = fs.readFileSync(name); - f.should.have.length(len); + var f = fs.readFileSync(name); + f.should.have.length(len); f[0].should.have.equal(i); } fs.removeSync(tmp_path); - done(); + done(); } - } - catch (e) { + } + catch (e) { try { fs.removeSync(tmp_path); } catch (e1) { } - done(e); - } + done(e); + } }); for(var i = 0; i < file_count; i++) { var data = new Buffer(len); @@ -583,6 +583,50 @@ describe('file Nodes', function() { }); }); + it('should write to multiple files if node is closed', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, "createDir":true, wires: [["helperNode1"]]}, + {id:"helperNode1", type:"helper"}]; + var tmp_path = path.join(resourcesDir, "tmp"); + var len = 1024*1024*10; + var file_count = 5; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + var n2 = helper.getNode("helperNode1"); + var count = 0; + n2.on("input", function(msg) { + try { + count++; + if (count == file_count) { + for(var i = 0; i < file_count; i++) { + var name = path.join(tmp_path, String(i)); + var f = fs.readFileSync(name); + f.should.have.length(len); + f[0].should.have.equal(i); + } + fs.removeSync(tmp_path); + done(); + } + } + catch (e) { + try { + fs.removeSync(tmp_path); + } + catch (e1) { + } + done(e); + } + }); + for(var i = 0; i < file_count; i++) { + var data = new Buffer(len); + data.fill(i); + var name = path.join(tmp_path, String(i)); + var msg = {payload:data, filename:name}; + n1.receive(msg); + } + n1.close(); + }); + }); + }); @@ -615,7 +659,7 @@ describe('file Nodes', function() { it('should read in a file and output a buffer', function(done) { var flow = [{id:"fileInNode1", type:"file in", name:"fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -632,7 +676,7 @@ describe('file Nodes', function() { it('should read in a file and output a utf8 string', function(done) { var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"utf8", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -653,7 +697,7 @@ describe('file Nodes', function() { it('should read in a file and output split lines with parts', function(done) { var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -691,7 +735,7 @@ describe('file Nodes', function() { var line = data.join("\n"); fs.writeFileSync(fileToTest, line); var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -724,7 +768,7 @@ describe('file Nodes', function() { it('should read in a file and output a buffer with parts', function(done) { var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"stream", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); From 7cec7ae60805aeb453da2259e76904e980260907 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sun, 30 Sep 2018 22:30:19 +0900 Subject: [PATCH 09/38] invoke callbacks if async handler is specified --- nodes/core/storage/50-file.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 853f93186..51ffb3e58 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -31,6 +31,7 @@ module.exports = function(RED) { node.data = []; node.msgQueue = []; node.closing = false; + node.closeCallbacks = []; function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; @@ -180,10 +181,19 @@ module.exports = function(RED) { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } node.status({}); + var callbacks = node.closeCallbacks; + node.closeCallbacks = []; node.closing = false; + for (cb in callbacks) { + cb(); + } } - this.on('close', function() { + this.on('close', function(arg1, arg2) { + var cb = arg2 ? arg2 : arg1; + if (cb) { + node.closeCallbacks.push(done); + } if (node.closing) { // already closing return; From 58c8311d56142ee4faff84f74953349399204777 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 2 Oct 2018 20:37:30 +0900 Subject: [PATCH 10/38] make close handler argument only one --- nodes/core/storage/50-file.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 51ffb3e58..43195901f 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -189,8 +189,7 @@ module.exports = function(RED) { } } - this.on('close', function(arg1, arg2) { - var cb = arg2 ? arg2 : arg1; + this.on('close', function(cb) { if (cb) { node.closeCallbacks.push(done); } From 3d70bc722ae270a85cc74d8b14955beeea203a76 Mon Sep 17 00:00:00 2001 From: Osamu Katada Date: Wed, 3 Oct 2018 09:58:25 +0900 Subject: [PATCH 11/38] Add http-proxy for http-request node. --- .../@node-red/nodes/core/io/06-httpproxy.html | 136 ++++++++++++++++++ .../@node-red/nodes/core/io/06-httpproxy.js | 33 +++++ .../nodes/core/io/21-httprequest.html | 33 ++++- .../@node-red/nodes/core/io/21-httprequest.js | 17 +++ .../nodes/locales/en-US/messages.json | 4 + .../nodes/locales/ja/io/06-httpproxy.html | 22 +++ .../nodes/locales/ja/io/21-httprequest.html | 2 +- .../@node-red/nodes/locales/ja/messages.json | 4 + test/nodes/core/io/21-httprequest_spec.js | 132 +++++++++++++++++ 9 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html create mode 100644 packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js create mode 100644 packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html diff --git a/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html new file mode 100644 index 000000000..e4d03d1db --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html @@ -0,0 +1,136 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js new file mode 100644 index 000000000..abcee66f6 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js @@ -0,0 +1,33 @@ +/** + * 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. + **/ + +module.exports = function(RED) { + 'use strict'; + + function HTTPProxyConfig(n) { + RED.nodes.createNode(this, n); + this.name = n.name; + this.url = n.url; + this.noproxy = n.noproxy; + }; + + RED.nodes.registerType('http proxy', HTTPProxyConfig, { + credentials: { + username: {type:'text'}, + password: {type:'password'} + } + }); +}; diff --git a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html index f3c4c0228..e0d1f5ba2 100644 --- a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html @@ -53,6 +53,13 @@ +
+ + +
+ +
+
@@ -112,7 +119,7 @@ url to be constructed using values of the incoming message. For example, if the url is set to example.com/{{{topic}}}, it will have the value of msg.topic automatically inserted. Using {{{...}}} prevents mustache from escaping characters like / & etc.

-

Note: If running behind a proxy, the standard http_proxy=... environment variable should be set and Node-RED restarted.

+

Note: If running behind a proxy, the standard http_proxy=... environment variable should be set and Node-RED restarted, or use Proxy Configuration. If Proxy Configuration was set, the configuration take precedence over environment variable.

Using multiple HTTP Request nodes

In order to use more than one of these nodes in the same flow, care must be taken with the msg.headers property. The first node will set this property with @@ -141,7 +148,8 @@ method:{value:"GET"}, ret: {value:"txt"}, url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, - tls: {type:"tls-config",required: false} + tls: {type:"tls-config",required: false}, + proxy: {type:"http proxy",required: false} }, credentials: { user: {type:"text"}, @@ -192,6 +200,24 @@ $("#node-input-usetls").on("click",function() { updateTLSOptions(); }); + + function updateProxyOptions() { + if ($("#node-input-useProxy").is(":checked")) { + $("#node-input-useProxy-row").show(); + } else { + $("#node-input-useProxy-row").hide(); + } + } + if (this.proxy) { + $("#node-input-useProxy").prop("checked", true); + } else { + $("#node-input-useProxy").prop("checked", false); + } + updateProxyOptions(); + $("#node-input-useProxy").on("click", function() { + updateProxyOptions(); + }); + $("#node-input-ret").change(function() { if ($("#node-input-ret").val() === "obj") { $("#tip-json").show(); @@ -204,6 +230,9 @@ if (!$("#node-input-usetls").is(':checked')) { $("#node-input-tls").val("_ADD_"); } + if (!$("#node-input-useProxy").is(":checked")) { + $("#node-input-proxy").val("_ADD_"); + } } }); diff --git a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js index 28d67d077..59f4f8fdc 100644 --- a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js @@ -41,6 +41,13 @@ module.exports = function(RED) { if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); } if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); } + var proxyConfig = null; + if (n.proxy) { + proxyConfig = RED.nodes.getNode(n.proxy); + prox = proxyConfig.url; + noprox = proxyConfig.noproxy; + } + this.on("input",function(msg) { var preRequestTimestamp = process.hrtime(); node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"}); @@ -84,6 +91,7 @@ module.exports = function(RED) { opts.encoding = null; // Force NodeJs to return a Buffer (instead of a string) opts.maxRedirects = 21; opts.jar = request.jar(); + opts.proxy = null; var ctSet = "Content-Type"; // set default camel case var clSet = "Content-Length"; if (msg.headers) { @@ -206,6 +214,15 @@ module.exports = function(RED) { opts.proxy = null; } } + if (proxyConfig && proxyConfig.credentials && opts.proxy == proxyConfig.url) { + var proxyUsername = proxyConfig.credentials.username || ''; + var proxyPassword = proxyConfig.credentials.password || ''; + if (proxyUsername || proxyPassword) { + opts.headers['proxy-authorization'] = + 'Basic ' + + Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64'); + } + } if (tlsNode) { tlsNode.addTLSOptions(opts); } else { diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index a8f9490f0..65894d80c 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -383,6 +383,10 @@ "basicauth": "Use basic authentication", "use-tls": "Enable secure (SSL/TLS) connection", "tls-config":"TLS Configuration", + "use-proxy": "Use proxy", + "proxy-config": "Proxy Configuration", + "use-proxyauth": "Use proxy authentication", + "noproxy-hosts": "Ignore hosts", "utf8": "a UTF-8 string", "binary": "a binary buffer", "json": "a parsed JSON object", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html new file mode 100644 index 000000000..03beddb8d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html @@ -0,0 +1,22 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html index 662367343..a22be8f3d 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html @@ -49,7 +49,7 @@

詳細

ノードの設定でurlプロパティを指定する場合、mustache形式のタグを含めることができます。これにより、URLを入力メッセージの値から構成することができます。例えば、urlがexample.com/{{{topic}}}の場合、msg.topicの値による置き換えを自動的に行います。{{{...}}}表記を使うと、/、&といった文字をmustacheがエスケープするのを抑止できます。

-

: proxyサーバを利用している場合、環境変数http_proxy=...を設定して、Node-REDを再起動してください。

+

: proxyサーバを利用している場合、環境変数http_proxy=...を設定して、Node-REDを再起動するか、あるいはプロキシ設定をしてください。

複数のHTTPリクエストノードの利用

同一フローで本ノードを複数利用するためには、msg.headersプロパティの扱いに注意しなくてはなりません。例えば、最初のノードがレスポンスヘッダにこのプロパティを設定し、次のノードがこのプロパティをリクエストヘッダに利用するというのは一般的には期待する動作ではありません。msg.headersプロパティをノード間で変更しないままとすると、2つ目のノードで無視されることになります。カスタムヘッダを設定するためには、msg.headersをまず削除もしくは空のオブジェクト{}にリセットします。

クッキーの扱い

diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index f160164d3..9f5102eb3 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -383,6 +383,10 @@ "basicauth": "ベーシック認証を使用", "use-tls": "SSL/TLS接続を有効化", "tls-config": "TLS設定", + "use-proxy": "プロキシを使用", + "proxy-config": "プロキシ設定", + "use-proxyauth": "プロキシ認証を使用", + "noproxy-hosts": "例外ホスト", "utf8": "文字列", "binary": "バイナリバッファ", "json": "JSON", diff --git a/test/nodes/core/io/21-httprequest_spec.js b/test/nodes/core/io/21-httprequest_spec.js index e7e8625b3..1633d6c38 100644 --- a/test/nodes/core/io/21-httprequest_spec.js +++ b/test/nodes/core/io/21-httprequest_spec.js @@ -24,6 +24,7 @@ var stoppable = require('stoppable'); var helper = require("node-red-node-test-helper"); var httpRequestNode = require("nr-test-utils").require("@node-red/nodes/core/io/21-httprequest.js"); var tlsNode = require("nr-test-utils").require("@node-red/nodes/core/io/05-tls.js"); +var httpProxyNode = require("nr-test-utils").require("@node-red/nodes/core/io/06-httpproxy.js"); var hashSum = require("hash-sum"); var httpProxy = require('http-proxy'); var cookieParser = require('cookie-parser'); @@ -1194,6 +1195,80 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo"}); }); }); + + it('should use http-proxy-config', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"}, + {id:"n2",type:"helper"}, + {id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should not use http-proxy-config when invalid url is specified', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"invalidvalue"} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use http-proxy-config when valid noproxy is specified', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort,noproxy:["foo","localhost"]} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); }); describe('authentication', function() { @@ -1264,6 +1339,63 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo"}); }); }); + + it('should authenticate on proxy server(http-proxy-config)', function(done) { + var flow = [ + {id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n3.credentials = {username:'foouser', password:'barpassword'}; + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('user', 'foouser'); + msg.payload.should.have.property('pass', 'barpassword'); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should output an error when proxy authentication was failed(http-proxy-config)', function(done) { + var flow = [ + {id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"http://@localhost:" + testProxyPort} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n3.credentials = {username:'xxxuser', password:'barpassword'}; + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',407); + msg.headers.should.have.property('proxy-authenticate', 'BASIC realm="test"'); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); }); describe('redirect-cookie', function() { From 71403e5acd33e86d4a73f241cc8eb2c740014479 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 3 Oct 2018 13:39:11 +0900 Subject: [PATCH 12/38] Update test cases in theme_spec.js --- .../editor-api/lib/editor/theme_spec.js | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js index 62d2b10fd..b7f3e62ec 100644 --- a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js @@ -26,46 +26,52 @@ var NR_TEST_UTILS = require("nr-test-utils"); var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"); -describe("api/editor/theme", function() { - beforeEach(function() { - sinon.stub(fs,"statSync",function() { return true; }); +describe("api/editor/theme", function () { + beforeEach(function () { + sinon.stub(fs, "statSync", function () { return true; }); }); - afterEach(function() { - theme.init({settings:{}}); + afterEach(function () { + theme.init({settings: {}}); fs.statSync.restore(); }); - it("applies the default theme", function() { + it("applies the default theme", function () { var result = theme.init({}); should.not.exist(result); var context = theme.context(); context.should.have.a.property("page"); - context.page.should.have.a.property("title","Node-RED"); - context.page.should.have.a.property("favicon","favicon.ico"); + context.page.should.have.a.property("title", "Node-RED"); + context.page.should.have.a.property("favicon", "favicon.ico"); + context.page.should.have.a.property("tabicon", "red/images/node-red-icon-black.svg"); context.should.have.a.property("header"); - context.header.should.have.a.property("title","Node-RED"); - context.header.should.have.a.property("image","red/images/node-red.png"); + context.header.should.have.a.property("title", "Node-RED"); + context.header.should.have.a.property("image", "red/images/node-red.png"); + context.should.have.a.property("asset"); + context.asset.should.have.a.property("red", "red/red.min.js"); + context.asset.should.have.a.property("main", "red/main.min.js"); should.not.exist(theme.settings()); }); - it("picks up custom theme", function() { + it("picks up custom theme", function () { theme.init({ editorTheme: { page: { title: "Test Page Title", - favicon: "/absolute/path/to/theme/icon", + favicon: "/absolute/path/to/theme/favicon", + tabicon: "/absolute/path/to/theme/tabicon", css: "/absolute/path/to/custom/css/file.css", scripts: "/absolute/path/to/script.js" }, header: { title: "Test Header Title", + url: "http://nodered.org", image: "/absolute/path/to/header/image" // or null to remove image }, deployButton: { - type:"simple", - label:"Save", + type: "simple", + label: "Save", icon: "/absolute/path/to/deploy/button/image" // or null to remove image }, @@ -83,6 +89,16 @@ describe("api/editor/theme", function() { login: { image: "/absolute/path/to/login/page/big/image" // a 256x256 image + }, + + palette: { + editable: true, + catalogues: ['https://catalogue.nodered.org/catalogue.json'], + theme: [{ category: ".*", type: ".*", color: "#f0f" }] + }, + + projects: { + enabled: false } } }); @@ -91,22 +107,40 @@ describe("api/editor/theme", function() { var context = theme.context(); context.should.have.a.property("page"); - context.page.should.have.a.property("title","Test Page Title"); + context.page.should.have.a.property("title", "Test Page Title"); + context.page.should.have.a.property("favicon", "theme/favicon/favicon"); + context.page.should.have.a.property("tabicon", "theme/tabicon/tabicon"); context.should.have.a.property("header"); - context.header.should.have.a.property("title","Test Header Title"); + context.header.should.have.a.property("title", "Test Header Title"); + context.header.should.have.a.property("url", "http://nodered.org"); + context.header.should.have.a.property("image", "theme/header/image"); context.page.should.have.a.property("css"); context.page.css.should.have.lengthOf(1); context.page.css[0].should.eql('theme/css/file.css'); - context.page.should.have.a.property("scripts"); context.page.scripts.should.have.lengthOf(1); context.page.scripts[0].should.eql('theme/scripts/script.js'); + context.should.have.a.property("login"); + context.login.should.have.a.property("image", "theme/login/image"); var settings = theme.settings(); settings.should.have.a.property("deployButton"); + settings.deployButton.should.have.a.property("type", "simple"); + settings.deployButton.should.have.a.property("label", "Save"); + settings.deployButton.should.have.a.property("icon", "theme/deploy/image"); settings.should.have.a.property("userMenu"); + settings.userMenu.should.be.eql(false); settings.should.have.a.property("menu"); - + settings.menu.should.have.a.property("menu-item-import-library", false); + settings.menu.should.have.a.property("menu-item-export-library", false); + settings.menu.should.have.a.property("menu-item-keyboard-shortcuts", false); + settings.menu.should.have.a.property("menu-item-help", { label: "Alternative Help Link Text", url: "http://example.com" }); + settings.should.have.a.property("palette"); + settings.palette.should.have.a.property("editable", true); + settings.palette.should.have.a.property("catalogues", ['https://catalogue.nodered.org/catalogue.json']); + settings.palette.should.have.a.property("theme", [{ category: ".*", type: ".*", color: "#f0f" }]); + settings.should.have.a.property("projects"); + settings.projects.should.have.a.property("enabled", false); }); }); From 144104245818679711f0a1509cc4dccd450e39a7 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Wed, 3 Oct 2018 21:29:28 +0900 Subject: [PATCH 13/38] update close & input handling of File node --- nodes/core/storage/50-file.js | 166 ++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 43195901f..936e2f84f 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -28,10 +28,9 @@ module.exports = function(RED) { this.createDir = n.createDir || false; var node = this; node.wstream = null; - node.data = []; node.msgQueue = []; node.closing = false; - node.closeCallbacks = []; + node.closeCallback = null; function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; @@ -76,83 +75,81 @@ module.exports = function(RED) { if (typeof data === "boolean") { data = data.toString(); } if (typeof data === "number") { data = data.toString(); } if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; } - node.data.push({msg:msg,data:Buffer.from(data)}); - - while (node.data.length > 0) { - if (node.overwriteFile === "true") { - (function(packet) { - node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true }); - node.wstream.on("error", function(err) { - node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); - }); - node.wstream.on("open", function() { - node.wstream.end(packet.data, function() { - node.send(packet.msg); - done(); - }); - }) - })(node.data.shift()); - } - else { - // Append mode - var recreateStream = !node.wstream || !node.filename; - if (node.wstream && node.wstreamIno) { - // There is already a stream open and we have the inode - // of the file. Check the file hasn't been deleted - // or deleted and recreated. - try { - var stat = fs.statSync(filename); - // File exists - check the inode matches - if (stat.ino !== node.wstreamIno) { - // The file has been recreated. Close the current - // stream and recreate it - recreateStream = true; - node.wstream.end(); - delete node.wstream; - delete node.wstreamIno; - } - } catch(err) { - // File does not exist + if (node.overwriteFile === "true") { + var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true }); + node.wstream = wstream; + wstream.on("error", function(err) { + node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); + done(); + }); + wstream.on("open", function() { + wstream.end(data, function() { + node.send(msg); + done(); + }); + }) + return; + } + else { + // Append mode + var recreateStream = !node.wstream || !node.filename; + if (node.wstream && node.wstreamIno) { + // There is already a stream open and we have the inode + // of the file. Check the file hasn't been deleted + // or deleted and recreated. + try { + var stat = fs.statSync(filename); + // File exists - check the inode matches + if (stat.ino !== node.wstreamIno) { + // The file has been recreated. Close the current + // stream and recreate it recreateStream = true; node.wstream.end(); delete node.wstream; delete node.wstreamIno; } - } - if (recreateStream) { - node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true }); - node.wstream.on("open", function(fd) { - try { - var stat = fs.statSync(filename); - node.wstreamIno = stat.ino; - } catch(err) { - } - }); - node.wstream.on("error", function(err) { - node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); - }); - } - if (node.filename) { - // Static filename - write and reuse the stream next time - var packet = node.data.shift() - node.wstream.write(packet.data, function() { - node.send(packet.msg); - done(); - }); - - } else { - // Dynamic filename - write and close the stream - var packet = node.data.shift() - node.wstream.end(packet.data, function() { - node.send(packet.msg); - delete node.wstream; - delete node.wstreamIno; - done(); - }); + } catch(err) { + // File does not exist + recreateStream = true; + node.wstream.end(); + delete node.wstream; + delete node.wstreamIno; } } + if (recreateStream) { + node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true }); + node.wstream.on("open", function(fd) { + try { + var stat = fs.statSync(filename); + node.wstreamIno = stat.ino; + } catch(err) { + } + }); + node.wstream.on("error", function(err) { + node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); + done(); + }); + } + if (node.filename) { + // Static filename - write and reuse the stream next time + node.wstream.write(data, function() { + node.send(msg); + done(); + }); + } else { + // Dynamic filename - write and close the stream + node.wstream.end(data, function() { + node.send(msg); + delete node.wstream; + delete node.wstreamIno; + done(); + }); + } } } + else { + done(); + } } function processQ(queue) { @@ -162,6 +159,9 @@ module.exports = function(RED) { if (queue.length > 0) { processQ(queue); } + else if (node.closing) { + closeNode(); + } }); } @@ -171,9 +171,15 @@ module.exports = function(RED) { // pending write exists return; } - processQ(msgQueue); - if (node.closing) { - closeNode(); + try { + processQ(msgQueue); + } + catch (e) { + node.msgQueue = []; + if (node.closing) { + closeNode(); + } + throw e; } }); @@ -181,23 +187,23 @@ module.exports = function(RED) { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } node.status({}); - var callbacks = node.closeCallbacks; - node.closeCallbacks = []; + var cb = node.closeCallback; + node.closeCallback = null; node.closing = false; - for (cb in callbacks) { + if (cb) { cb(); } } - this.on('close', function(cb) { - if (cb) { - node.closeCallbacks.push(done); - } + this.on('close', function(done) { if (node.closing) { // already closing return; } node.closing = true; + if (done) { + node.closeCallback = done; + } if (node.msgQueue.length > 0) { // close after queue processed return; From 14435c24acec74d770fb589b91ddfaac1c90b558 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 5 Oct 2018 21:16:57 +0900 Subject: [PATCH 14/38] fix i18n handling for ja-JP locale on Safari/MacOS --- .../editor-api/lib/editor/locales.js | 14 ++++++++++- .../editor-api/lib/editor/locales_spec.js | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js index ecf21d1ff..5e10647e7 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js @@ -21,6 +21,18 @@ var i18n = require("@node-red/util").i18n; // TODO: separate module var runtimeAPI; +function loadResource(lang, namespace) { + var catalog = i18n.i.getResourceBundle(lang, namespace); + if (!catalog) { + var parts = lang.split("-"); + if (parts.length == 2) { + var new_lang = parts[0]; + return i18n.i.getResourceBundle(new_lang, namespace); + } + } + return catalog; +} + module.exports = { init: function(_runtimeAPI) { runtimeAPI = _runtimeAPI; @@ -33,7 +45,7 @@ module.exports = { var prevLang = i18n.i.language; // Trigger a load from disk of the language if it is not the default i18n.i.changeLanguage(lang, function(){ - var catalog = i18n.i.getResourceBundle(lang, namespace); + var catalog = loadResource(lang, namespace); res.json(catalog||{}); }); i18n.i.changeLanguage(prevLang); diff --git a/test/unit/@node-red/editor-api/lib/editor/locales_spec.js b/test/unit/@node-red/editor-api/lib/editor/locales_spec.js index d9cb1c600..2de9d4799 100644 --- a/test/unit/@node-red/editor-api/lib/editor/locales_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/locales_spec.js @@ -91,6 +91,30 @@ describe("api/editor/locales", function() { done(); }); }); + + it('returns for locale defined only with primary tag ', function(done) { + var orig = i18n.i.getResourceBundle; + i18n.i.getResourceBundle = function (lang, ns) { + if (lang === "ja-JP") { + return undefined; + } + return orig(lang, ns); + }; + request(app) + // returns `ja` instead of `ja-JP` + .get("/locales/message-catalog?lng=ja-JP") + .expect(200) + .end(function(err,res) { + i18n.i.getResourceBundle = orig; + if (err) { + return done(err); + } + res.body.should.have.property('namespace','message-catalog'); + res.body.should.have.property('lang','ja'); + done(); + }); + }); + }); // describe('get all node resource catalogs',function() { From 8235b7b96d1e8a437afde5b3da5fb53647521363 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Thu, 11 Oct 2018 18:16:08 +0900 Subject: [PATCH 15/38] Followed runtime-editor split --- package.json | 1 + test/editor/editor_helper.js | 2 +- test/editor/pageobjects/editor/workspace_page.js | 2 +- test/editor/pageobjects/nodes/core/core/58-debug_page.js | 2 +- .../editor/pageobjects/nodes/core/logic/15-change_page.js | 8 ++++---- test/editor/specs/scenario/cookbook_endpoint_uispec.js | 5 +++-- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index b5d15970d..570173117 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "istanbul": "0.4.5", "minami": "1.2.3", "mocha": "^5.2.0", + "mosca": "^2.8.3", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.0.6", diff --git a/test/editor/editor_helper.js b/test/editor/editor_helper.js index 281988484..656ebe5c7 100644 --- a/test/editor/editor_helper.js +++ b/test/editor/editor_helper.js @@ -21,7 +21,7 @@ var fs = require('fs-extra'); var path = require('path'); var app = express(); -var RED = require("../../red/red.js"); +var RED = require("../../packages/node_modules/node-red/lib/red.js"); var utilPage = require("./pageobjects/util/util_page"); diff --git a/test/editor/pageobjects/editor/workspace_page.js b/test/editor/pageobjects/editor/workspace_page.js index e62eb686f..7eca9d25a 100644 --- a/test/editor/pageobjects/editor/workspace_page.js +++ b/test/editor/pageobjects/editor/workspace_page.js @@ -16,7 +16,7 @@ var when = require("when"); -var events = require("../../../../red/runtime/events.js"); +var events = require("../../../../packages/node_modules/@node-red/runtime/lib/events.js"); var palette = require("./palette_page"); var nodeFactory = require("../nodes/nodefactory_page"); diff --git a/test/editor/pageobjects/nodes/core/core/58-debug_page.js b/test/editor/pageobjects/nodes/core/core/58-debug_page.js index c8c1d8871..0dc4c298e 100644 --- a/test/editor/pageobjects/nodes/core/core/58-debug_page.js +++ b/test/editor/pageobjects/nodes/core/core/58-debug_page.js @@ -29,7 +29,7 @@ debugNode.prototype.setOutput = function(complete) { browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]/button'); if (complete !== 'true') { // Select the "msg" type. - browser.clickWithWait('/html/body/div[11]/a[1]'); + browser.clickWithWait('//div[@class="red-ui-typedInput-options"][1]/a[1]'); // Input the path in msg. browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input'); browser.keys(['Control', 'a', 'Control']); diff --git a/test/editor/pageobjects/nodes/core/logic/15-change_page.js b/test/editor/pageobjects/nodes/core/logic/15-change_page.js index 1336e8069..51589f7b5 100644 --- a/test/editor/pageobjects/nodes/core/logic/15-change_page.js +++ b/test/editor/pageobjects/nodes/core/logic/15-change_page.js @@ -54,8 +54,8 @@ changeNode.prototype.ruleSet = function(p, pt, to, tot, index) { setT("set", index); if (pt) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); - var num = 5 * index + 6; - var ptXPath = '/html/body/div[' + num + ']/a[' + ptType[pt] + ']'; + var num = 5 * (index - 1) + 1; + var ptXPath = '//div[@class="red-ui-typedInput-options"][' + num + ']/a[' + ptType[pt] + ']'; browser.clickWithWait(ptXPath); } if (p) { @@ -63,8 +63,8 @@ changeNode.prototype.ruleSet = function(p, pt, to, tot, index) { } if (tot) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]'); - var num = 5 * index + 7; - var totXPath = '/html/body/div[' + num + ']/a[' + totType[tot] + ']'; + var num = 5 * (index - 1) + 2; + var totXPath = '//div[@class="red-ui-typedInput-options"][' + num + ']/a[' + totType[tot] + ']'; browser.clickWithWait(totXPath); } if (to) { diff --git a/test/editor/specs/scenario/cookbook_endpoint_uispec.js b/test/editor/specs/scenario/cookbook_endpoint_uispec.js index bb54da557..da672a747 100644 --- a/test/editor/specs/scenario/cookbook_endpoint_uispec.js +++ b/test/editor/specs/scenario/cookbook_endpoint_uispec.js @@ -315,8 +315,9 @@ describe('cookbook', function() { debugTab.open(); debugTab.clearMessage(); injectNode.clickLeftButton(); - var message = debugTab.getMessage(); - message[1].indexOf('application/json').should.not.eql(-1); + var messages = debugTab.getMessage(); + var contents = messages.join([separator = ""]); + contents.indexOf('application/json').should.not.eql(-1); }); it('serve a local file', function () { From 925ebcc06e365288493f5946a0a02a94c53af6db Mon Sep 17 00:00:00 2001 From: YuLun Shih Date: Fri, 12 Oct 2018 14:50:45 -0700 Subject: [PATCH 16/38] Add missing comma --- settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.js b/settings.js index 6d6c6948b..641c56498 100644 --- a/settings.js +++ b/settings.js @@ -148,7 +148,7 @@ module.exports = { // The following property can be used to cause insecure HTTP connections to // be redirected to HTTPS. - //requireHttps: true + //requireHttps: true, // The following property can be used to disable the editor. The admin API // is not affected by this option. To disable both the editor and the admin From f204c77ba3614970313e54ac00bb22f3750ea11e Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Tue, 16 Oct 2018 10:29:58 +0900 Subject: [PATCH 17/38] Modify require paths --- test/editor/editor_helper.js | 2 +- test/editor/pageobjects/editor/workspace_page.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/editor/editor_helper.js b/test/editor/editor_helper.js index 656ebe5c7..2cf103188 100644 --- a/test/editor/editor_helper.js +++ b/test/editor/editor_helper.js @@ -21,7 +21,7 @@ var fs = require('fs-extra'); var path = require('path'); var app = express(); -var RED = require("../../packages/node_modules/node-red/lib/red.js"); +var RED = require("nr-test-utils").require("node-red/lib/red.js"); var utilPage = require("./pageobjects/util/util_page"); diff --git a/test/editor/pageobjects/editor/workspace_page.js b/test/editor/pageobjects/editor/workspace_page.js index 7eca9d25a..e1a57d86d 100644 --- a/test/editor/pageobjects/editor/workspace_page.js +++ b/test/editor/pageobjects/editor/workspace_page.js @@ -16,7 +16,7 @@ var when = require("when"); -var events = require("../../../../packages/node_modules/@node-red/runtime/lib/events.js"); +var events = require("nr-test-utils").require("@node-red/runtime/lib/events.js"); var palette = require("./palette_page"); var nodeFactory = require("../nodes/nodefactory_page"); From fc8c4063f22fa4dae450665eb9b8eb3677e55835 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 16 Oct 2018 11:36:24 +0100 Subject: [PATCH 18/38] Add markdown toolbar to flow description editor --- .../src/js/ui/editors/markdown.js | 3 ++ .../editor-client/src/js/ui/workspaces.js | 28 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js index dd020e780..e75e1b617 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js @@ -199,6 +199,9 @@ RED.editor.types._markdown = (function() { } } else { editor.session.replace(editor.selection.getRange(), (style.before||"")+current+(style.after||"")); + if (current === "") { + editor.gotoLine(range.start.row+1,range.start.column+(style.before||"").length,false); + } } editor.focus(); }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 284bd6243..bb8fac23b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -150,13 +150,14 @@ RED.workspaces = (function() { $('
'+ ''+ - ' '+ + ' '+ ''+ '
').appendTo(dialogForm); - $('
'+ + var row = $('
'+ ''+ - '
'+ + '
'+ + '
'+ '
').appendTo(dialogForm); tabflowEditor = RED.editor.createEditor({ id: 'node-input-info', @@ -164,7 +165,26 @@ RED.workspaces = (function() { value: "" }); - $('
').appendTo(dialogForm); + var toolbar = RED.editor.types._markdown.buildToolbar(row.find(".node-text-editor-toolbar"),tabflowEditor); + + $('').appendTo(toolbar); + + $('#node-info-input-info-expand').click(function(e) { + e.preventDefault(); + var value = tabflowEditor.getValue(); + RED.editor.editMarkdown({ + value: value, + width: "Infinity", + cursor: tabflowEditor.getCursorPosition(), + complete: function(v,cursor) { + tabflowEditor.setValue(v, -1); + tabflowEditor.gotoLine(cursor.row+1,cursor.column,false); + setTimeout(function() { + tabflowEditor.focus(); + },300); + } + }) + }); dialogForm.find('#node-input-disabled-btn').on("click",function(e) { var i = $(this).find("i"); From d40aa7260f405dc2d449891d08f496c444692431 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 16 Oct 2018 11:36:46 +0100 Subject: [PATCH 19/38] Mark all newly imported nodes as changed --- packages/node_modules/@node-red/editor-client/src/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 9746f5e68..ece448c64 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -2747,7 +2747,7 @@ RED.view = (function() { 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};}); - var new_node_ids = new_nodes.map(function(n){ return n.id; }); + var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); // TODO: pick a more sensible root node if (new_ms.length > 0) { From 51a352183421a3a23079b766fad03bc7f9d93436 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Tue, 16 Oct 2018 19:48:50 +0900 Subject: [PATCH 20/38] Fix that unnecessary optionMenu remains --- editor/js/ui/common/typedInput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/js/ui/common/typedInput.js b/editor/js/ui/common/typedInput.js index 6aab0bf53..5f78a2c74 100644 --- a/editor/js/ui/common/typedInput.js +++ b/editor/js/ui/common/typedInput.js @@ -537,6 +537,10 @@ } else { this.selectLabel.text(opt.label); } + if (this.optionMenu) { + this.optionMenu.remove(); + this.optionMenu = null; + } if (opt.options) { if (this.optionExpandButton) { this.optionExpandButton.hide(); @@ -627,10 +631,6 @@ } } } else { - if (this.optionMenu) { - this.optionMenu.remove(); - this.optionMenu = null; - } if (this.optionSelectTrigger) { this.optionSelectTrigger.hide(); } From dc3128fb3e02a2e308764b7eef6b2e39d1510918 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 16 Oct 2018 14:05:23 +0100 Subject: [PATCH 21/38] Add node module into to sidebar and palette popover --- .../editor-client/locales/en-US/editor.json | 1 + .../editor-client/src/js/ui/palette.js | 23 +++++++++++++++---- .../editor-client/src/js/ui/tab-info.js | 13 +++++++---- 3 files changed, 28 insertions(+), 9 deletions(-) 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 84b4f025b..46523bc65 100644 --- 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 @@ -440,6 +440,7 @@ "label": "info", "node": "Node", "type": "Type", + "module": "Module", "id": "ID", "status": "Status", "enabled": "Enabled", 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 6d5f0315f..3fbb77545 100644 --- 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 @@ -110,13 +110,26 @@ RED.palette = (function() { var popOverContent; try { var l = "

"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"

"; - if (label != type) { - l = "

"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"
"+type+"

"; - } - popOverContent = $(l+(info?info:$("script[data-help-name='"+type+"']").html()||"

"+RED._("palette.noInfo")+"

").trim()) + popOverContent = $('
').append($(l+(info?info:$("script[data-help-name='"+type+"']").html()||"

"+RED._("palette.noInfo")+"

").trim()) .filter(function(n) { return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0) - }).slice(0,2); + }).slice(0,2)); + popOverContent.find("a").each(function(){ + var linkText = $(this).text(); + $(this).before(linkText); + $(this).remove(); + }); + + var typeInfo = RED.nodes.getType(type); + + if (typeInfo) { + var metaData = ""; + if (typeInfo && !/^subflow:/.test(type)) { + metaData = typeInfo.set.module+" : "; + } + metaData += type; + $('

',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent); + } } catch(err) { // Malformed HTML may cause errors. TODO: need to understand what can break // NON-NLS: internal debug diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index cc834475f..834eb40ec 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -207,6 +207,7 @@ RED.sidebar.info = (function() { $('').prependTo($(propRow.children()[1])) } } + var count = 0; if (!m && node.type != "subflow") { var defaults; if (node.type === 'unknown') { @@ -218,9 +219,13 @@ RED.sidebar.info = (function() { }) } else if (node._def) { defaults = node._def.defaults; + propRow = $(''+RED._("sidebar.info.module")+"").appendTo(tableBody); + $(propRow.children()[1]).text(RED.nodes.getType(node.type).set.module); + count++; } + $('').appendTo(tableBody); + if (defaults) { - var count = 0; for (var n in defaults) { if (n != "name" && n != "info" && defaults.hasOwnProperty(n)) { var val = node[n]; @@ -254,9 +259,9 @@ RED.sidebar.info = (function() { } } } - if (count > 0) { - $(''+RED._("sidebar.info.showMore")+''+RED._("sidebar.info.showLess")+' ').appendTo(tableBody); - } + } + if (count > 0) { + $(''+RED._("sidebar.info.showMore")+''+RED._("sidebar.info.showLess")+' ').appendTo(tableBody); } } if (node.type !== 'tab') { From 1064e531f0322115713d2af2821da6c4611f3371 Mon Sep 17 00:00:00 2001 From: nakanishi Date: Wed, 17 Oct 2018 10:31:47 +0900 Subject: [PATCH 22/38] Adjust the fa icon position for the node with height --- packages/node_modules/@node-red/editor-client/src/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index ece448c64..a97f9fd17 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1913,7 +1913,6 @@ RED.view = (function() { .attr("xlink:href",iconUrl) .attr("class","fa-lg") .attr("x",15) - .attr("y",21) .attr("stroke","none") .attr("fill","#ffffff") .attr("text-anchor","middle") @@ -2414,6 +2413,7 @@ RED.view = (function() { thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;}); thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)}); + thisNode.selectAll(".fa-lg").attr("y",function(d){return (d.h+13)/2;}); thisNode.selectAll(".node_button").attr("opacity",function(d) { return (activeSubflow||!isButtonEnabled(d))?0.4:1 From ce014044ea02570d9781d9acec4c22f81aa87e16 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 17 Oct 2018 15:43:09 +0900 Subject: [PATCH 23/38] Fix reference bug in message catalog --- .../runtime/lib/nodes/context/index.js | 4 +-- .../runtime/locales/en-US/runtime.json | 7 ++-- .../@node-red/runtime/locales/ja/runtime.json | 35 +++++++++---------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js index 6f63344e3..2716d2ad8 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js @@ -104,7 +104,7 @@ function load() { try { plugin = require("./"+plugins[pluginName].module); } catch(err) { - return reject(new Error(log._("context.error-loading-module", {module:plugins[pluginName].module,message:err.toString()}))); + return reject(new Error(log._("context.error-loading-module2", {module:plugins[pluginName].module,message:err.toString()}))); } } else { // Assume `module` is an already-required module we can use @@ -123,7 +123,7 @@ function load() { } log.info(log._("context.log-store-init", {name:pluginName, info:"module="+moduleInfo})); } catch(err) { - return reject(new Error(log._("context.error-loading-module",{module:pluginName,message:err.toString()}))); + return reject(new Error(log._("context.error-loading-module2",{module:pluginName,message:err.toString()}))); } } else { // Plugin does not specify a 'module' diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index 2a328c0bb..8d47d8215 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -30,11 +30,10 @@ "installed": "Installed module: __name__", "install-failed": "Install failed", "install-failed-long": "Installation of module __name__ failed:", - "install-failed-not-found": "$t(install-failed-long) module not found", + "install-failed-not-found": "$t(server.install.install-failed-long) module not found", "upgrading": "Upgrading module: __name__ to version: __version__", "upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version", "upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found", - "install-failed-not-found": "$t(server.install.install-failed-long) module not found", "uninstalling": "Uninstalling module: __name__", "uninstall-failed": "Uninstall failed", "uninstall-failed-long": "Uninstall of module __name__ failed:", @@ -160,12 +159,12 @@ "context": { "log-store-init": "Context store : '__name__' [__info__]", - "error-loading-module": "Error loading context store '__module__': __message__ ", + "error-loading-module": "Error loading context store: __message__", + "error-loading-module2": "Error loading context store '__module__': __message__", "error-module-not-defined": "Context store '__storage__' missing 'module' option", "error-invalid-module-name": "Invalid context store name: '__name__'", "error-invalid-default-module": "Default context store unknown: '__storage__'", "unknown-store": "Unknown context store '__name__' specified. Using default store.", - "error-loading-module": "Error loading context store: __message__", "localfilesystem": { "error-circular": "Context __scope__ contains a circular reference that cannot be persisted", "error-write": "Error writing context: __message__" diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index 9eee93651..916519351 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -8,7 +8,6 @@ "httpStatic": "HTTP Static : __path__" } }, - "server": { "loading": "パレットノードのロード", "palette-editor": { @@ -30,11 +29,10 @@ "installed": "モジュール __name__ をインストールしました", "install-failed": "インストールに失敗しました", "install-failed-long": "モジュール __name__ のインストールに失敗しました:", - "install-failed-not-found": "$t(install-failed-long) モジュールが見つかりません", + "install-failed-not-found": "$t(server.install.install-failed-long) モジュールが見つかりません", "upgrading": "モジュール __name__ をバージョン __version__ に更新します", "upgraded": "モジュール __name__ を更新しました。新しいバージョンを使うには、Node-REDを再起動してください。", "upgrade-failed-not-found": "$t(server.install.install-failed-long) バージョンが見つかりません", - "install-failed-not-found": "$t(server.install.install-failed-long) モジュールが見つかりません", "uninstalling": "モジュールをアンインストールします: __name__", "uninstall-failed": "アンインストールに失敗しました", "uninstall-failed-long": "モジュール __name__ のアンインストールに失敗しました:", @@ -49,7 +47,6 @@ "headless-mode": "ヘッドレスモードで実行中です", "httpadminauth-deprecated": "httpAdminAuthは非推奨です。代わりに adminAuth を使用してください" }, - "api": { "flows": { "error-save": "フローの保存エラー: __message__", @@ -67,27 +64,25 @@ "error-enable": "ノードの有効化に失敗しました:" } }, - "comms": { "error": "通信チャネルエラー: __message__", "error-server": "サーバエラー: __message__", "error-send": "送信エラー: __message__" }, - "settings": { "user-not-available": "ユーザ設定を保存できません: __message__", "not-available": "設定が利用できません", "property-read-only": "プロパティ '__prop__' は読み出し専用です" }, - "nodes": { "credentials": { - "error":"クレデンシャルの読み込みエラー: __message__", - "error-saving":"クレデンシャルの保存エラー: __message__", + "error": "クレデンシャルの読み込みエラー: __message__", + "error-saving": "クレデンシャルの保存エラー: __message__", "not-registered": "クレデンシャル '__type__' は登録されていません", "system-key-warning": "\n\n---------------------------------------------------------------------\nフローのクレデンシャルファイルはシステム生成キーで暗号化されています。\n\nシステム生成キーを何らかの理由で失った場合、クレデンシャルファイルを\n復元することはできません。その場合、ファイルを削除してクレデンシャルを\n再入力しなければなりません。\n\n設定ファイル内で 'credentialSecret' オプションを使って独自キーを設定\nします。変更を次にデプロイする際、Node-REDは選択したキーを用いてクレ\nデンシャルを再暗号化します。 \n\n---------------------------------------------------------------------\n" }, "flows": { + "safe-mode": "セーフモードでフローを停止しました。開始するためにはデプロイしてください", "registered-missing": "欠落しているノードを登録します: __type__", "error": "フローの読み込みエラー: __message__", "starting-modified-nodes": "更新されたノードを開始します", @@ -128,7 +123,6 @@ } } }, - "storage": { "index": { "forbidden-flow-name": "不正なフロー名" @@ -156,14 +150,17 @@ } } }, - "context": { - "log-store-init": "コンテクストストア : '__name__' [__info__]", - "error-loading-module": "コンテクストストア '__module__' のロードでエラーが発生しました: __message__ ", - "error-module-not-defined": "コンテクストストア '__storage__' に 'module' オプションが指定されていません", - "error-invalid-module-name": "不正なコンテクストストア名: '__name__'", - "error-invalid-default-module": "デフォルトコンテクストストアが不明: '__storage__'", - "unknown-store": "不明なコンテクストストア '__name__' が指定されました。デフォルトストアを使用します。" - } - + "log-store-init": "コンテキストストア : '__name__' [__info__]", + "error-loading-module": "コンテキストストアのロードでエラーが発生しました: __message__", + "error-loading-module2": "コンテキストストア '__module__' のロードでエラーが発生しました: __message__", + "error-module-not-defined": "コンテキストストア '__storage__' に 'module' オプションが指定されていません", + "error-invalid-module-name": "不正なコンテキストストア名: '__name__'", + "error-invalid-default-module": "デフォルトコンテキストストアが不明: '__storage__'", + "unknown-store": "不明なコンテキストストア '__name__' が指定されました。デフォルトストアを使用します。", + "localfilesystem": { + "error-circular": "コンテキスト __scope__ は永続化できない循環参照を含んでいます", + "error-write": "コンテキスト書込みエラー: __message__" + } + } } From 0f793ebd65c8feeada9c00f84b12fdc1c8097e7e Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Wed, 17 Oct 2018 18:03:21 +0900 Subject: [PATCH 24/38] Update Japanese message catalog --- .../editor-client/locales/ja/editor.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 3a7570784..eb1ae260e 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -78,6 +78,12 @@ "projects-settings": "設定" } }, + "actions": { + "toggle-navigator": "ナビゲータの表示/非表示を切替", + "zoom-out": "縮小", + "zoom-reset": "拡大/縮小を初期化", + "zoom-in": "拡大" + }, "user": { "loggedInAs": "__name__ としてログインしました", "username": "ユーザ名", @@ -334,6 +340,10 @@ "analysis": "分析", "advanced": "その他" }, + "actions": { + "collapse-all": "全カテゴリを折畳む", + "expand-all": "全カテゴリを展開" + }, "event": { "nodeAdded": "ノードをパレットへ追加しました:", "nodeAdded_plural": "ノードをパレットへ追加しました", @@ -486,7 +496,9 @@ "editDescription": "プロジェクトの詳細を編集", "editDependencies": "プロジェクトの依存関係を編集", "editReadme": "README.mdを編集", + "showProjectSettings": "プロジェクト設定を表示", "projectSettings": { + "title": "プロジェクト設定", "edit": "編集", "none": "なし", "install": "インストール", @@ -726,7 +738,7 @@ "repo-not-found": "リポジトリが見つかりません" }, "default-files": { - "create": "プロジェクト関連ファアイルの作成", + "create": "プロジェクト関連ファイルの作成", "desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。", "desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。", "desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。", @@ -742,7 +754,7 @@ "desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。", "desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。", "desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。", - "desc7": "キーはプロジェクトファイルとば別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。", + "desc7": "キーはプロジェクトファイルとは別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。", "credentials": "認証情報", "enable": "暗号化を有効にする", "disable": "暗号化を無効にする", From 289583325d3e74f9916034cab049b6ecc16b3666 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 17 Oct 2018 11:03:09 +0100 Subject: [PATCH 25/38] Allow palette to be hidden --- .../editor-client/locales/en-US/editor.json | 3 ++ .../editor-client/src/js/keymap.json | 1 + .../@node-red/editor-client/src/js/red.js | 1 + .../editor-client/src/js/ui/palette.js | 36 ++++++++++++++++++- .../editor-client/src/js/ui/sidebar.js | 25 +++++++++++++ .../editor-client/src/js/ui/tab-info.js | 15 ++++++++ .../editor-client/src/sass/palette.scss | 8 +++++ .../editor-client/src/sass/sidebar.scss | 27 ++++++++++++++ .../editor-client/src/sass/workspace.scss | 6 +++- 9 files changed, 120 insertions(+), 2 deletions(-) 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 46523bc65..9a8800df5 100644 --- 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 @@ -46,6 +46,9 @@ "sidebar": { "show": "Show sidebar" }, + "palette": { + "show": "Show palette" + }, "settings": "Settings", "userSettings": "User Settings", "nodes": "Nodes", 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 dc37232e2..5b7a94f1f 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 @@ -14,6 +14,7 @@ "ctrl-e": "core:show-export-dialog", "ctrl-i": "core:show-import-dialog", "ctrl-space": "core:toggle-sidebar", + "ctrl-p": "core:toggle-palette", "ctrl-,": "core:show-user-settings", "ctrl-alt-r": "core:show-remote-diff", "ctrl-alt-n": "core:new-project", diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index d294c8376..4d8012cf4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -434,6 +434,7 @@ var RED = (function() { // {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}} // ]}, // null, + {id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true}, {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true}, null ]}); 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 3fbb77545..4740ada58 100644 --- 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 @@ -20,7 +20,7 @@ RED.palette = (function() { var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced']; var categoryContainers = {}; - + var sidebarControls; function createCategory(originalCategory,rootCategory,category,ns) { if ($("#palette-base-category-"+rootCategory).length === 0) { @@ -502,6 +502,20 @@ RED.palette = (function() { } }) + sidebarControls = $('