mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge branch 'master' into dev
This commit is contained in:
		
							
								
								
									
										18
									
								
								.github/scripts/update-node-red-website.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/scripts/update-node-red-website.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | const fs = require("fs"); | ||||||
|  |  | ||||||
|  | const newVersion = require("../../package.json").version; | ||||||
|  |  | ||||||
|  | if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) { | ||||||
|  |     console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`); | ||||||
|  |     process.exit(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (!/^\d+\.\d+\.\d+$/.test(newVersion)) { | ||||||
|  |     console.log(`Not updating for a non-stable release - ${newVersion}`); | ||||||
|  |     process.exit(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const path = __dirname+"/../../../node-red.github.io/index.html"; | ||||||
|  | let contents = fs.readFileSync(path, "utf8"); | ||||||
|  | contents = contents.replace(/<span class="node-red-latest-version">v\d+\.\d+\.\d+<\/span>/, `<span class="node-red-latest-version">v${newVersion}<\/span>` ); | ||||||
|  | fs.writeFileSync(path, contents); | ||||||
							
								
								
									
										22
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,12 +18,16 @@ jobs: | |||||||
|         with: |         with: | ||||||
|             repository: 'node-red/node-red-docker' |             repository: 'node-red/node-red-docker' | ||||||
|             path: 'node-red-docker' |             path: 'node-red-docker' | ||||||
|  |       - name: Check out node-red.github.io repository | ||||||
|  |         uses: actions/checkout@v2 | ||||||
|  |         with: | ||||||
|  |             repository: 'node-red/node-red.github.io' | ||||||
|  |             path: 'node-red.github.io' | ||||||
|       - uses: actions/setup-node@v1 |       - uses: actions/setup-node@v1 | ||||||
|         with: |         with: | ||||||
|             node-version: '12' |             node-version: '12' | ||||||
|       - run: node ./node-red/.github/scripts/update-node-red-docker.js |       - run: node ./node-red/.github/scripts/update-node-red-docker.js | ||||||
|         id: updateFiles |       - name: Create Docker Pull Request | ||||||
|       - name: Create Pull Request |  | ||||||
|         uses: peter-evans/create-pull-request@v2 |         uses: peter-evans/create-pull-request@v2 | ||||||
|         with: |         with: | ||||||
|           token: ${{ secrets.NR_REPO_TOKEN }} |           token: ${{ secrets.NR_REPO_TOKEN }} | ||||||
| @@ -37,4 +41,18 @@ jobs: | |||||||
|  |  | ||||||
|             Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`. |             Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`. | ||||||
|  |  | ||||||
|  |             This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary | ||||||
|  |       - run: node ./node-red/.github/scripts/update-node-red-website.js | ||||||
|  |       - name: Create Website Pull Request | ||||||
|  |         uses: peter-evans/create-pull-request@v2 | ||||||
|  |         with: | ||||||
|  |           token: ${{ secrets.NR_REPO_TOKEN }} | ||||||
|  |           committer: GitHub <noreply@github.com> | ||||||
|  |           author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> | ||||||
|  |           path: 'node-red.github.io' | ||||||
|  |           commit-message: 'Bump to ${{ env.newVersion }}' | ||||||
|  |           title: '🚀 Update to Node-RED ${{ env.newVersion }} release' | ||||||
|  |           body: | | ||||||
|  |             Updates the Node-RED Website repo for the ${{ env.newVersion }} release. | ||||||
|  |  | ||||||
|             This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary |             This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,41 @@ | |||||||
|  | ### 1.1.3: Maintenance Release | ||||||
|  |  | ||||||
|  | Editor | ||||||
|  |  - Fix vertical align of fa node icons Fixes #2670 | ||||||
|  |  - Allow lasso selection to be restricted to active group | ||||||
|  |  - Make ctrl-click on nested group more intuitive | ||||||
|  |  - Fix copy/paste of nested groups | ||||||
|  |  - Add Set(iterable) polyfill for IE11 | ||||||
|  |  - Support select-all inside active group | ||||||
|  |  - Improve performance of moving groups | ||||||
|  |  - Add additional check for git auth failure response Fixes #2656 | ||||||
|  |  - german translation, wording (#2660) (#2666) | ||||||
|  |  - Remove filtering of duplicate fa icons | ||||||
|  |  - Show node help when switching node edit dialogs Fixes #2652 | ||||||
|  |  - Ensure group theme picks up theme defaults properly Fixes #2651 | ||||||
|  |  | ||||||
|  | Nodes | ||||||
|  |  - Clarify Switch node isEmpty help | ||||||
|  |  - HTTP In: handle application/cbor as binary | ||||||
|  |  | ||||||
|  | Runtime | ||||||
|  |  - Move runtime settings back to adminApi from editorApi Fixes #2662 | ||||||
|  |  - Update Chinese message for debug node | ||||||
|  |  | ||||||
|  | ### 1.1.2: Maintenance Release | ||||||
|  |  | ||||||
|  | Editor | ||||||
|  |  | ||||||
|  |  - Fix all the touch screen issues Fixes #2647 | ||||||
|  |  - Add RED.view.redrawStatus to avoid full redraw on update | ||||||
|  |  - Ensure node/group xrefs are consistent on import | ||||||
|  |  - Disable keyboard handler when dialogs are open | ||||||
|  |  - Ensure unknown nodes removed from outliner when node registers Fixes #2646 | ||||||
|  |  | ||||||
|  | Runtime | ||||||
|  |  | ||||||
|  |  - Allow Comms websocket auth to be done via token header Fixes #2642 | ||||||
|  |  | ||||||
| ### 1.1.1: Maintenance Release | ### 1.1.1: Maintenance Release | ||||||
|  |  | ||||||
| Editor | Editor | ||||||
|   | |||||||
| @@ -21,15 +21,17 @@ var flows = require("./flows"); | |||||||
| var flow = require("./flow"); | var flow = require("./flow"); | ||||||
| var context = require("./context"); | var context = require("./context"); | ||||||
| var auth = require("../auth"); | var auth = require("../auth"); | ||||||
|  | var info = require("./settings"); | ||||||
|  |  | ||||||
| var apiUtil = require("../util"); | var apiUtil = require("../util"); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     init: function(runtimeAPI) { |     init: function(settings,runtimeAPI) { | ||||||
|         flows.init(runtimeAPI); |         flows.init(runtimeAPI); | ||||||
|         flow.init(runtimeAPI); |         flow.init(runtimeAPI); | ||||||
|         nodes.init(runtimeAPI); |         nodes.init(runtimeAPI); | ||||||
|         context.init(runtimeAPI); |         context.init(runtimeAPI); | ||||||
|  |         info.init(settings,runtimeAPI); | ||||||
|  |  | ||||||
|         var needsPermission = auth.needsPermission; |         var needsPermission = auth.needsPermission; | ||||||
|  |  | ||||||
| @@ -67,6 +69,8 @@ module.exports = { | |||||||
|         // adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler); |         // adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler); | ||||||
|         adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler); |         adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler); | ||||||
|  |  | ||||||
|  |         adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); | ||||||
|  |  | ||||||
|         return adminApp; |         return adminApp; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								packages/node_modules/@node-red/editor-api/lib/admin/settings.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								packages/node_modules/@node-red/editor-api/lib/admin/settings.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright JS Foundation and other contributors, http://js.foundation | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  * http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  **/ | ||||||
|  | var apiUtils = require("../util"); | ||||||
|  | var runtimeAPI; | ||||||
|  | var settings; | ||||||
|  | var theme = require("../editor/theme"); | ||||||
|  | var clone = require("clone"); | ||||||
|  |  | ||||||
|  | var i18n = require("@node-red/util").i18n | ||||||
|  |  | ||||||
|  | function extend(target, source) { | ||||||
|  |     var keys = Object.keys(source); | ||||||
|  |     var i = keys.length; | ||||||
|  |     while(i--) { | ||||||
|  |         var value = source[keys[i]] | ||||||
|  |         var type = typeof value; | ||||||
|  |         if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) { | ||||||
|  |             target[keys[i]] = value; | ||||||
|  |         } else if (value === null) { | ||||||
|  |             if (target.hasOwnProperty(keys[i])) { | ||||||
|  |                 delete target[keys[i]]; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // Object | ||||||
|  |             if (target.hasOwnProperty(keys[i])) { | ||||||
|  |                 target[keys[i]] = extend(target[keys[i]],value); | ||||||
|  |             } else { | ||||||
|  |                 target[keys[i]] = value; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return target; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     init: function(_settings,_runtimeAPI) { | ||||||
|  |         runtimeAPI = _runtimeAPI; | ||||||
|  |         settings = _settings; | ||||||
|  |     }, | ||||||
|  |     runtimeSettings: function(req,res) { | ||||||
|  |         var opts = { | ||||||
|  |             user: req.user | ||||||
|  |         } | ||||||
|  |         runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) { | ||||||
|  |             if (!settings.disableEditor) { | ||||||
|  |                 result.editorTheme = result.editorTheme||{}; | ||||||
|  |                 var themeSettings = theme.settings(); | ||||||
|  |                 if (themeSettings) { | ||||||
|  |                     // result.editorTheme may already exist with the palette | ||||||
|  |                     // disabled. Need to merge that into the receive settings | ||||||
|  |                     result.editorTheme = extend(clone(themeSettings),result.editorTheme); | ||||||
|  |                 } | ||||||
|  |                 result.editorTheme.languages = i18n.availableLanguages("editor"); | ||||||
|  |             } | ||||||
|  |             res.json(result); | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -123,15 +123,16 @@ AnonymousStrategy.prototype.authenticate = function(req) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function TokensStrategy() { |  | ||||||
|   passport.Strategy.call(this); | function authenticateUserToken(req) { | ||||||
|   this.name = 'tokens'; |     return new Promise( (resolve,reject) => { | ||||||
| } |  | ||||||
| util.inherits(TokensStrategy, passport.Strategy); |  | ||||||
| TokensStrategy.prototype.authenticate = function(req) { |  | ||||||
|     var self = this; |  | ||||||
|         var token = null; |         var token = null; | ||||||
|     if (Users.tokenHeader() === 'authorization') {   |         var tokenHeader = Users.tokenHeader(); | ||||||
|  |         if (Users.tokenHeader() === null) { | ||||||
|  |             // No custom user token provided. Fail the request | ||||||
|  |             reject(); | ||||||
|  |             return; | ||||||
|  |         } else if (Users.tokenHeader() === 'authorization') { | ||||||
|             if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { |             if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { | ||||||
|                 token = req.headers.authorization.split(' ')[1]; |                 token = req.headers.authorization.split(' ')[1]; | ||||||
|             } |             } | ||||||
| @@ -139,22 +140,40 @@ TokensStrategy.prototype.authenticate = function(req) { | |||||||
|             token = req.headers[Users.tokenHeader()]; |             token = req.headers[Users.tokenHeader()]; | ||||||
|         } |         } | ||||||
|         if (token) { |         if (token) { | ||||||
|         Users.tokens(token).then(function(admin) { |             Users.tokens(token).then(function(user) { | ||||||
|             if (admin) { |                 if (user) { | ||||||
|                 self.success(admin,{scope:admin.permissions}); |                     resolve(user); | ||||||
|                 } else { |                 } else { | ||||||
|                 self.fail(401); |                     reject(); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|         self.fail(401); |             reject(); | ||||||
|         } |         } | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function TokensStrategy() { | ||||||
|  |   passport.Strategy.call(this); | ||||||
|  |   this.name = 'tokens'; | ||||||
|  | } | ||||||
|  | util.inherits(TokensStrategy, passport.Strategy); | ||||||
|  | TokensStrategy.prototype.authenticate = function(req) { | ||||||
|  |     authenticateUserToken(req).then(user => { | ||||||
|  |         this.success(user,{scope:user.permissions}); | ||||||
|  |     }).catch(err => { | ||||||
|  |         this.fail(401); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     bearerStrategy: bearerStrategy, |     bearerStrategy: bearerStrategy, | ||||||
|     clientPasswordStrategy: clientPasswordStrategy, |     clientPasswordStrategy: clientPasswordStrategy, | ||||||
|     passwordTokenExchange: passwordTokenExchange, |     passwordTokenExchange: passwordTokenExchange, | ||||||
|     anonymousStrategy: new AnonymousStrategy(), |     anonymousStrategy: new AnonymousStrategy(), | ||||||
|     tokensStrategy: new TokensStrategy() |     tokensStrategy: new TokensStrategy(), | ||||||
|  |     authenticateUserToken: authenticateUserToken | ||||||
| } | } | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ var api = { | |||||||
|     authenticate: authenticate, |     authenticate: authenticate, | ||||||
|     default: getDefaultUser, |     default: getDefaultUser, | ||||||
|     tokens: getDefaultUser, |     tokens: getDefaultUser, | ||||||
|     tokenHeader: "authorization" |     tokenHeader: null | ||||||
| } | } | ||||||
|  |  | ||||||
| function init(config) { | function init(config) { | ||||||
| @@ -111,6 +111,8 @@ function init(config) { | |||||||
|         api.tokens = config.tokens; |         api.tokens = config.tokens; | ||||||
|         if (config.tokenHeader && typeof config.tokenHeader === "string") { |         if (config.tokenHeader && typeof config.tokenHeader === "string") { | ||||||
|             api.tokenHeader = config.tokenHeader.toLowerCase(); |             api.tokenHeader = config.tokenHeader.toLowerCase(); | ||||||
|  |         } else { | ||||||
|  |             api.tokenHeader = "authorization"; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ var log = require("@node-red/util").log; // TODO: separate module | |||||||
| var Tokens; | var Tokens; | ||||||
| var Users; | var Users; | ||||||
| var Permissions; | var Permissions; | ||||||
|  | var Strategies; | ||||||
|  |  | ||||||
| var server; | var server; | ||||||
| var settings; | var settings; | ||||||
| @@ -44,6 +45,7 @@ function init(_server,_settings,_runtimeAPI) { | |||||||
|     Tokens.onSessionExpiry(handleSessionExpiry); |     Tokens.onSessionExpiry(handleSessionExpiry); | ||||||
|     Users = require("../auth/users"); |     Users = require("../auth/users"); | ||||||
|     Permissions = require("../auth/permissions"); |     Permissions = require("../auth/permissions"); | ||||||
|  |     Strategies = require("../auth/strategies"); | ||||||
|  |  | ||||||
| } | } | ||||||
| function handleSessionExpiry(session) { | function handleSessionExpiry(session) { | ||||||
| @@ -63,17 +65,18 @@ function generateSession(length) { | |||||||
|     return token.join(""); |     return token.join(""); | ||||||
| } | } | ||||||
|  |  | ||||||
| function CommsConnection(ws) { | function CommsConnection(ws, user) { | ||||||
|     this.session = generateSession(32); |     this.session = generateSession(32); | ||||||
|     this.ws = ws; |     this.ws = ws; | ||||||
|     this.stack = []; |     this.stack = []; | ||||||
|     this.user = null; |     this.user = user; | ||||||
|     this.lastSentTime = 0; |     this.lastSentTime = 0; | ||||||
|     var self = this; |     var self = this; | ||||||
|  |  | ||||||
|     log.audit({event: "comms.open"}); |     log.audit({event: "comms.open"}); | ||||||
|     log.trace("comms.open "+self.session); |     log.trace("comms.open "+self.session); | ||||||
|     var pendingAuth = (settings.adminAuth != null); |     var preAuthed = !!user; | ||||||
|  |     var pendingAuth = !this.user && (settings.adminAuth != null); | ||||||
|  |  | ||||||
|     if (!pendingAuth) { |     if (!pendingAuth) { | ||||||
|         addActiveConnection(self); |         addActiveConnection(self); | ||||||
| @@ -199,8 +202,8 @@ function start() { | |||||||
|             var commsPath = settings.httpAdminRoot || "/"; |             var commsPath = settings.httpAdminRoot || "/"; | ||||||
|             commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms"; |             commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms"; | ||||||
|             wsServer = new ws.Server({ noServer: true }); |             wsServer = new ws.Server({ noServer: true }); | ||||||
|             wsServer.on('connection',function(ws) { |             wsServer.on('connection',function(ws, request, user) { | ||||||
|                 var commsConnection = new CommsConnection(ws); |                 var commsConnection = new CommsConnection(ws, user); | ||||||
|             }); |             }); | ||||||
|             wsServer.on('error', function(err) { |             wsServer.on('error', function(err) { | ||||||
|                 log.warn(log._("comms.error-server",{message:err.toString()})); |                 log.warn(log._("comms.error-server",{message:err.toString()})); | ||||||
| @@ -209,8 +212,26 @@ function start() { | |||||||
|             server.on('upgrade', function upgrade(request, socket, head) { |             server.on('upgrade', function upgrade(request, socket, head) { | ||||||
|                 const pathname = url.parse(request.url).pathname; |                 const pathname = url.parse(request.url).pathname; | ||||||
|                 if (pathname === commsPath) { |                 if (pathname === commsPath) { | ||||||
|  |                     if (Users.tokenHeader() !== null && request.headers[Users.tokenHeader()]) { | ||||||
|  |                         // The user has provided custom token handling. For the websocket, | ||||||
|  |                         // the token could be provided in two ways: | ||||||
|  |                         //  - as an http header (only possible with a reverse proxy setup) | ||||||
|  |                         //  - passed over the connected websock in an auth packet | ||||||
|  |                         // If the header is present, verify the token. If not, use the auth | ||||||
|  |                         // packet over the connected socket | ||||||
|  |                         // | ||||||
|  |                         Strategies.authenticateUserToken(request).then(user => { | ||||||
|                             wsServer.handleUpgrade(request, socket, head, function done(ws) { |                             wsServer.handleUpgrade(request, socket, head, function done(ws) { | ||||||
|                         wsServer.emit('connection', ws, request); |                                 wsServer.emit('connection', ws, request, user); | ||||||
|  |                             }); | ||||||
|  |                         }).catch(err => { | ||||||
|  |                             log.audit({event: "comms.auth.fail"}); | ||||||
|  |                             socket.destroy(); | ||||||
|  |                         }) | ||||||
|  |                         return | ||||||
|  |                     } | ||||||
|  |                     wsServer.handleUpgrade(request, socket, head, function done(ws) { | ||||||
|  |                         wsServer.emit('connection', ws, request, null); | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
|                 // Don't destroy the socket as other listeners may want to handle the |                 // Don't destroy the socket as other listeners may want to handle the | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ module.exports = { | |||||||
|             editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler); |             editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler); | ||||||
|  |  | ||||||
|             // Settings |             // Settings | ||||||
|             editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); |             //  Main /settings route is an admin route - see lib/admin/settings.js | ||||||
|             // User Settings |             // User Settings | ||||||
|             editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler); |             editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler); | ||||||
|             // User Settings |             // User Settings | ||||||
|   | |||||||
| @@ -16,56 +16,12 @@ | |||||||
| var apiUtils = require("../util"); | var apiUtils = require("../util"); | ||||||
| var runtimeAPI; | var runtimeAPI; | ||||||
| var sshkeys = require("./sshkeys"); | var sshkeys = require("./sshkeys"); | ||||||
| var theme = require("./theme"); |  | ||||||
| var clone = require("clone"); |  | ||||||
|  |  | ||||||
| var i18n = require("@node-red/util").i18n |  | ||||||
|  |  | ||||||
| function extend(target, source) { |  | ||||||
|     var keys = Object.keys(source); |  | ||||||
|     var i = keys.length; |  | ||||||
|     while(i--) { |  | ||||||
|         var value = source[keys[i]] |  | ||||||
|         var type = typeof value; |  | ||||||
|         if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) { |  | ||||||
|             target[keys[i]] = value; |  | ||||||
|         } else if (value === null) { |  | ||||||
|             if (target.hasOwnProperty(keys[i])) { |  | ||||||
|                 delete target[keys[i]]; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             // Object |  | ||||||
|             if (target.hasOwnProperty(keys[i])) { |  | ||||||
|                 target[keys[i]] = extend(target[keys[i]],value); |  | ||||||
|             } else { |  | ||||||
|                 target[keys[i]] = value; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return target; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     init: function(_runtimeAPI) { |     init: function(_runtimeAPI) { | ||||||
|         runtimeAPI = _runtimeAPI; |         runtimeAPI = _runtimeAPI; | ||||||
|         sshkeys.init(runtimeAPI); |         sshkeys.init(runtimeAPI); | ||||||
|     }, |     }, | ||||||
|     runtimeSettings: function(req,res) { |  | ||||||
|         var opts = { |  | ||||||
|             user: req.user |  | ||||||
|         } |  | ||||||
|         runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) { |  | ||||||
|             result.editorTheme = result.editorTheme||{}; |  | ||||||
|             var themeSettings = theme.settings(); |  | ||||||
|             if (themeSettings) { |  | ||||||
|                 // result.editorTheme may already exist with the palette |  | ||||||
|                 // disabled. Need to merge that into the receive settings |  | ||||||
|                 result.editorTheme = extend(clone(themeSettings),result.editorTheme); |  | ||||||
|             } |  | ||||||
|             result.editorTheme.languages = i18n.availableLanguages("editor"); |  | ||||||
|             res.json(result); |  | ||||||
|         }); |  | ||||||
|     }, |  | ||||||
|     userSettings: function(req, res) { |     userSettings: function(req, res) { | ||||||
|         var opts = { |         var opts = { | ||||||
|             user: req.user |             user: req.user | ||||||
|   | |||||||
| @@ -99,7 +99,7 @@ function init(settings,_server,storage,runtimeAPI) { | |||||||
|             adminApp.use(corsHandler); |             adminApp.use(corsHandler); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var adminApiApp = require("./admin").init(runtimeAPI); |         var adminApiApp = require("./admin").init(settings, runtimeAPI); | ||||||
|         adminApp.use(adminApiApp); |         adminApp.use(adminApiApp); | ||||||
|     } else { |     } else { | ||||||
|         adminApp = null; |         adminApp = null; | ||||||
|   | |||||||
| @@ -808,17 +808,7 @@ RED.nodes.fontAwesome = (function() { | |||||||
|         "fa-youtube": "\uf167", |         "fa-youtube": "\uf167", | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     var iconList = []; |     var iconList = Object.keys(iconMap); | ||||||
|     var isUsed = {}; |  | ||||||
|     Object.keys(iconMap).forEach(function(icon) { |  | ||||||
|         var unicode = iconMap[icon]; |  | ||||||
|         // skip icons with a same unicode |  | ||||||
|         if (isUsed[unicode] !== true) { |  | ||||||
|             iconList.push(icon); |  | ||||||
|             isUsed[unicode] = true; |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     isUsed = undefined; |  | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         getIconUnicode: function(name) { |         getIconUnicode: function(name) { | ||||||
|   | |||||||
| @@ -998,6 +998,7 @@ RED.nodes = (function() { | |||||||
|         var new_nodes = []; |         var new_nodes = []; | ||||||
|         var new_links = []; |         var new_links = []; | ||||||
|         var new_groups = []; |         var new_groups = []; | ||||||
|  |         var new_group_set = new Set(); | ||||||
|         var nid; |         var nid; | ||||||
|         var def; |         var def; | ||||||
|         var configNode; |         var configNode; | ||||||
| @@ -1326,6 +1327,7 @@ RED.nodes = (function() { | |||||||
|                         new_nodes.push(node); |                         new_nodes.push(node); | ||||||
|                     } else if (node.type === "group") { |                     } else if (node.type === "group") { | ||||||
|                         new_groups.push(node); |                         new_groups.push(node); | ||||||
|  |                         new_group_set.add(node.id); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -1433,16 +1435,23 @@ RED.nodes = (function() { | |||||||
|         var groupDepthMap = {}; |         var groupDepthMap = {}; | ||||||
|         for (i=0;i<new_groups.length;i++) { |         for (i=0;i<new_groups.length;i++) { | ||||||
|             n = new_groups[i]; |             n = new_groups[i]; | ||||||
|             if (n.g && node_map[n.g]) { |  | ||||||
|                 n.g = node_map[n.g].id; |             if (n.g && !new_group_set.has(n.g)) { | ||||||
|             } else { |  | ||||||
|                 delete n.g; |                 delete n.g; | ||||||
|             } |             } | ||||||
|             n.nodes = n.nodes.map(function(id) { |             n.nodes = n.nodes.map(function(id) { | ||||||
|                 return node_map[id]; |                 return node_map[id]; | ||||||
|             }) |             }) | ||||||
|             // Just in case the group references a node that doesn't exist for some reason |             // Just in case the group references a node that doesn't exist for some reason | ||||||
|             n.nodes = n.nodes.filter(function(v) { return !!v}); |             n.nodes = n.nodes.filter(function(v) { | ||||||
|  |                 if (v) { | ||||||
|  |                     // Repair any nodes that have forgotten they are in this group | ||||||
|  |                     if (v.g !== n.id) { | ||||||
|  |                         v.g = n.id; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return !!v | ||||||
|  |             }); | ||||||
|             if (!n.g) { |             if (!n.g) { | ||||||
|                 groupDepthMap[n.id] = 0; |                 groupDepthMap[n.id] = 0; | ||||||
|             } |             } | ||||||
| @@ -1663,6 +1672,7 @@ RED.nodes = (function() { | |||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         reimportList.push(convertNode(n)); |                         reimportList.push(convertNode(n)); | ||||||
|  |                         RED.events.emit('nodes:remove',n); | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|                     // Remove any links between nodes that are going to be reimported. |                     // Remove any links between nodes that are going to be reimported. | ||||||
|   | |||||||
| @@ -37,5 +37,21 @@ | |||||||
|             } |             } | ||||||
|             return result; |             return result; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (new Set([0]).size === 0) { | ||||||
|  |             // IE does not support passing an iterable to Set constructor | ||||||
|  |             var _Set = Set; | ||||||
|  |             /*global Set:true */ | ||||||
|  |             Set = function Set(iterable) { | ||||||
|  |                 var set = new _Set(); | ||||||
|  |                 if (iterable) { | ||||||
|  |                     iterable.forEach(set.add, set); | ||||||
|  |                 } | ||||||
|  |                 return set; | ||||||
|  |             }; | ||||||
|  |             Set.prototype = _Set.prototype; | ||||||
|  |             Set.prototype.constructor = Set; | ||||||
|  |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -372,7 +372,7 @@ var RED = (function() { | |||||||
|                 node.status = msg; |                 node.status = msg; | ||||||
|                 node.dirtyStatus = true; |                 node.dirtyStatus = true; | ||||||
|                 node.dirty = true; |                 node.dirty = true; | ||||||
|                 RED.view.redraw(); |                 RED.view.redrawStatus(node); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         RED.comms.subscribe("notification/node/#",function(topic,msg) { |         RED.comms.subscribe("notification/node/#",function(topic,msg) { | ||||||
|   | |||||||
| @@ -159,7 +159,11 @@ RED.clipboard = (function() { | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 ], |                 ], | ||||||
|  |                 open: function( event, ui ) { | ||||||
|  |                     RED.keyboard.disable(); | ||||||
|  |                 }, | ||||||
|                 close: function(e) { |                 close: function(e) { | ||||||
|  |                     RED.keyboard.enable(); | ||||||
|                     if (popover) { |                     if (popover) { | ||||||
|                         popover.close(true); |                         popover.close(true); | ||||||
|                         currentPopoverError = null; |                         currentPopoverError = null; | ||||||
|   | |||||||
| @@ -1630,6 +1630,7 @@ RED.editor = (function() { | |||||||
|             show: function() { |             show: function() { | ||||||
|                 if (editing_node) { |                 if (editing_node) { | ||||||
|                     RED.sidebar.info.refresh(editing_node); |                     RED.sidebar.info.refresh(editing_node); | ||||||
|  |                     RED.sidebar.help.show(editing_node.type, false); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -1836,6 +1837,7 @@ RED.editor = (function() { | |||||||
|             show: function() { |             show: function() { | ||||||
|                 if (editing_config_node) { |                 if (editing_config_node) { | ||||||
|                     RED.sidebar.info.refresh(editing_config_node); |                     RED.sidebar.info.refresh(editing_config_node); | ||||||
|  |                     RED.sidebar.help.show(type, false); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -105,18 +105,18 @@ RED.group = (function() { | |||||||
|                 cellHeight: 16, |                 cellHeight: 16, | ||||||
|                 cellMargin: 3, |                 cellMargin: 3, | ||||||
|                 none: true, |                 none: true, | ||||||
|                 opacity: style['stroke-opacity'] || 1.0 |                 opacity: style.hasOwnProperty('stroke-opacity')?style['stroke-opacity']:(defaultGroupStyle.hasOwnProperty('stroke-opacity')?defaultGroupStyle['stroke-opacity']:1.0) | ||||||
|             }).appendTo("#node-input-row-style-stroke"); |             }).appendTo("#node-input-row-style-stroke"); | ||||||
|             RED.colorPicker.create({ |             RED.colorPicker.create({ | ||||||
|                 id:"node-input-style-fill", |                 id:"node-input-style-fill", | ||||||
|                 value: style.fill || "none", |                 value: style.fill || defaultGroupStyle.fill ||"none", | ||||||
|                 palette: colorPalette, |                 palette: colorPalette, | ||||||
|                 cellPerRow: colorCount, |                 cellPerRow: colorCount, | ||||||
|                 cellWidth: 16, |                 cellWidth: 16, | ||||||
|                 cellHeight: 16, |                 cellHeight: 16, | ||||||
|                 cellMargin: 3, |                 cellMargin: 3, | ||||||
|                 none: true, |                 none: true, | ||||||
|                 opacity: style['fill-opacity'] || 1.0 |                 opacity: style.hasOwnProperty('fill-opacity')?style['fill-opacity']:(defaultGroupStyle.hasOwnProperty('fill-opacity')?defaultGroupStyle['fill-opacity']:1.0) | ||||||
|             }).appendTo("#node-input-row-style-fill"); |             }).appendTo("#node-input-row-style-fill"); | ||||||
|  |  | ||||||
|             createLayoutPicker({ |             createLayoutPicker({ | ||||||
| @@ -162,12 +162,6 @@ RED.group = (function() { | |||||||
|                 delete this.style.color; |                 delete this.style.color; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (this.style["stroke-opacity"] === "1") { |  | ||||||
|                 delete this.style["stroke-opacity"] |  | ||||||
|             } |  | ||||||
|             if (this.style["fill-opacity"] === "1") { |  | ||||||
|                 delete this.style["fill-opacity"] |  | ||||||
|             } |  | ||||||
|             var node = this; |             var node = this; | ||||||
|             ['stroke','fill','stroke-opacity','fill-opacity','color','label-position'].forEach(function(prop) { |             ['stroke','fill','stroke-opacity','fill-opacity','color','label-position'].forEach(function(prop) { | ||||||
|                 if (node.style[prop] === defaultGroupStyle[prop]) { |                 if (node.style[prop] === defaultGroupStyle[prop]) { | ||||||
|   | |||||||
| @@ -17,6 +17,8 @@ RED.keyboard = (function() { | |||||||
|  |  | ||||||
|     var isMac = /Mac/i.test(window.navigator.platform); |     var isMac = /Mac/i.test(window.navigator.platform); | ||||||
|  |  | ||||||
|  |     var handlersActive = true; | ||||||
|  |  | ||||||
|     var handlers = {}; |     var handlers = {}; | ||||||
|     var partialState; |     var partialState; | ||||||
|  |  | ||||||
| @@ -225,6 +227,9 @@ RED.keyboard = (function() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     d3.select(window).on("keydown",function() { |     d3.select(window).on("keydown",function() { | ||||||
|  |         if (!handlersActive) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         if (metaKeyCodes[d3.event.keyCode]) { |         if (metaKeyCodes[d3.event.keyCode]) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -570,6 +575,13 @@ RED.keyboard = (function() { | |||||||
|         return pane; |         return pane; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     function enable() { | ||||||
|  |         handlersActive = true; | ||||||
|  |     } | ||||||
|  |     function disable() { | ||||||
|  |         handlersActive = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         init: init, |         init: init, | ||||||
|         add: addHandler, |         add: addHandler, | ||||||
| @@ -579,7 +591,9 @@ RED.keyboard = (function() { | |||||||
|         }, |         }, | ||||||
|         revertToDefault: revertToDefault, |         revertToDefault: revertToDefault, | ||||||
|         formatKey: formatKey, |         formatKey: formatKey, | ||||||
|         validateKey: validateKey |         validateKey: validateKey, | ||||||
|  |         disable: disable, | ||||||
|  |         enable: enable | ||||||
|     } |     } | ||||||
|  |  | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -472,6 +472,8 @@ RED.library = (function() { | |||||||
|                 autoOpen: false, |                 autoOpen: false, | ||||||
|                 width: 800, |                 width: 800, | ||||||
|                 resizable: false, |                 resizable: false, | ||||||
|  |                 open: function( event, ui ) { RED.keyboard.disable() }, | ||||||
|  |                 close: function( event, ui ) { RED.keyboard.enable() }, | ||||||
|                 classes: { |                 classes: { | ||||||
|                     "ui-dialog": "red-ui-editor-dialog", |                     "ui-dialog": "red-ui-editor-dialog", | ||||||
|                     "ui-dialog-titlebar-close": "hide", |                     "ui-dialog-titlebar-close": "hide", | ||||||
| @@ -556,9 +558,11 @@ RED.library = (function() { | |||||||
|                     } |                     } | ||||||
|                 ], |                 ], | ||||||
|                 open: function(e) { |                 open: function(e) { | ||||||
|  |                     RED.keyboard.disable(); | ||||||
|                     $(this).parent().find(".ui-dialog-titlebar-close").hide(); |                     $(this).parent().find(".ui-dialog-titlebar-close").hide(); | ||||||
|                 }, |                 }, | ||||||
|                 close: function(e) { |                 close: function(e) { | ||||||
|  |                     RED.keyboard.enable(); | ||||||
|                     if (libraryEditor) { |                     if (libraryEditor) { | ||||||
|                         libraryEditor.destroy(); |                         libraryEditor.destroy(); | ||||||
|                         libraryEditor = null; |                         libraryEditor = null; | ||||||
|   | |||||||
| @@ -2263,6 +2263,12 @@ RED.projects = (function() { | |||||||
|                 autoOpen: false, |                 autoOpen: false, | ||||||
|                 width: 600, |                 width: 600, | ||||||
|                 resizable: false, |                 resizable: false, | ||||||
|  |                 open: function(e) { | ||||||
|  |                     RED.keyboard.disable(); | ||||||
|  |                 }, | ||||||
|  |                 close: function(e) { | ||||||
|  |                     RED.keyboard.enable(); | ||||||
|  |                 }, | ||||||
|                 classes: { |                 classes: { | ||||||
|                     "ui-dialog": "red-ui-editor-dialog", |                     "ui-dialog": "red-ui-editor-dialog", | ||||||
|                     "ui-dialog-titlebar-close": "hide", |                     "ui-dialog-titlebar-close": "hide", | ||||||
|   | |||||||
| @@ -261,10 +261,12 @@ RED.sidebar.help = (function() { | |||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function show(type) { |     function show(type, bringToFront) { | ||||||
|  |         if (bringToFront !== false) { | ||||||
|             RED.sidebar.show("help"); |             RED.sidebar.show("help"); | ||||||
|  |         } | ||||||
|         if (type) { |         if (type) { | ||||||
|             hideTOC(); |             // hideTOC(); | ||||||
|             showHelp(type); |             showHelp(type); | ||||||
|         } |         } | ||||||
|         resizeStack(); |         resizeStack(); | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -39,7 +39,11 @@ RED.user = (function() { | |||||||
|             closeOnEscape: !!opts.cancelable, |             closeOnEscape: !!opts.cancelable, | ||||||
|             width: 600, |             width: 600, | ||||||
|             resizable: false, |             resizable: false, | ||||||
|             draggable: false |             draggable: false, | ||||||
|  |             close: function( event, ui ) { | ||||||
|  |                 $("#node-dialog-login").dialog('destroy').remove(); | ||||||
|  |                 RED.keyboard.enable() | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         $("#node-dialog-login-fields").empty(); |         $("#node-dialog-login-fields").empty(); | ||||||
| @@ -98,10 +102,10 @@ RED.user = (function() { | |||||||
|                             data: body |                             data: body | ||||||
|                         }).done(function(data,textStatus,xhr) { |                         }).done(function(data,textStatus,xhr) { | ||||||
|                             RED.settings.set("auth-tokens",data); |                             RED.settings.set("auth-tokens",data); | ||||||
|                             $("#node-dialog-login").dialog('destroy').remove(); |  | ||||||
|                             if (opts.updateMenu) { |                             if (opts.updateMenu) { | ||||||
|                                 updateUserMenu(); |                                 updateUserMenu(); | ||||||
|                             } |                             } | ||||||
|  |                             $("#node-dialog-login").dialog("close"); | ||||||
|                             done(); |                             done(); | ||||||
|                         }).fail(function(jqXHR,textStatus,errorThrown) { |                         }).fail(function(jqXHR,textStatus,errorThrown) { | ||||||
|                             RED.settings.remove("auth-tokens"); |                             RED.settings.remove("auth-tokens"); | ||||||
| @@ -143,7 +147,8 @@ RED.user = (function() { | |||||||
|                 } |                 } | ||||||
|                 if (opts.cancelable) { |                 if (opts.cancelable) { | ||||||
|                     $("#node-dialog-login-cancel").button().on("click", function( event ) { |                     $("#node-dialog-login-cancel").button().on("click", function( event ) { | ||||||
|                         $("#node-dialog-login").dialog('destroy').remove(); |                         $("#node-dialog-login").dialog('close'); | ||||||
|  |  | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -152,8 +157,7 @@ RED.user = (function() { | |||||||
|                 $("#node-dialog-login-image").load(function() { |                 $("#node-dialog-login-image").load(function() { | ||||||
|                     dialog.dialog("open"); |                     dialog.dialog("open"); | ||||||
|                 }).attr("src",loginImageSrc); |                 }).attr("src",loginImageSrc); | ||||||
|  |                 RED.keyboard.disable(); | ||||||
|  |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -597,6 +597,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { | |||||||
|         padding: 4px; |         padding: 4px; | ||||||
|         color: $secondary-text-color; |         color: $secondary-text-color; | ||||||
|         font-size: 0.9em; |         font-size: 0.9em; | ||||||
|  |         line-height: 24px; | ||||||
|     } |     } | ||||||
|     button { |     button { | ||||||
|         float: right; |         float: right; | ||||||
|   | |||||||
| @@ -204,7 +204,7 @@ | |||||||
| .red-ui-palette-icon-fa { | .red-ui-palette-icon-fa { | ||||||
|     color: white; |     color: white; | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     top: 7px; |     top: calc(50% - 7px); | ||||||
|     left: 3px; |     left: 3px; | ||||||
| } | } | ||||||
| .red-ui-palette-node-small { | .red-ui-palette-node-small { | ||||||
|   | |||||||
| @@ -46,10 +46,10 @@ module.exports = function(RED) { | |||||||
|                     isText = true; |                     isText = true; | ||||||
|                 } else if (parsedType.type !== "application") { |                 } else if (parsedType.type !== "application") { | ||||||
|                     isText = false; |                     isText = false; | ||||||
|                 } else if (parsedType.subtype !== "octet-stream") { |                 } else if ((parsedType.subtype !== "octet-stream") && (parsedType.subtype !== "cbor")) { | ||||||
|                     checkUTF = true; |                     checkUTF = true; | ||||||
|                 } else { |                 } else { | ||||||
|                     // applicatino/octet-stream |                     // application/octet-stream or application/cbor | ||||||
|                     isText = false; |                     isText = false; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -604,7 +604,7 @@ | |||||||
|       "inputrange" : "von einem Eingabebereich", |       "inputrange" : "von einem Eingabebereich", | ||||||
|       "resultrange" : "in einen Zielbereich", |       "resultrange" : "in einen Zielbereich", | ||||||
|       "from" : "von", |       "from" : "von", | ||||||
|       "to" : "bis", |       "to" : "auf", | ||||||
|       "roundresult" : "Runde das Ergebnis auf die nächste ganze Zahl?" |       "roundresult" : "Runde das Ergebnis auf die nächste ganze Zahl?" | ||||||
|     }, |     }, | ||||||
|     "placeholder" : { |     "placeholder" : { | ||||||
|   | |||||||
| @@ -123,7 +123,7 @@ | |||||||
|         "none": "None", |         "none": "None", | ||||||
|         "invalid-exp": "无效的JSONata表达式: __error__", |         "invalid-exp": "无效的JSONata表达式: __error__", | ||||||
|         "msgprop": "信息属性", |         "msgprop": "信息属性", | ||||||
|         "msgobj": "完整信息", |         "msgobj": "与调试输出相同", | ||||||
|         "autostatus": "自动的", |         "autostatus": "自动的", | ||||||
|         "to": "目标", |         "to": "目标", | ||||||
|         "debtab": "调试窗口", |         "debtab": "调试窗口", | ||||||
|   | |||||||
| @@ -123,7 +123,7 @@ | |||||||
|         "none": "None", |         "none": "None", | ||||||
|         "invalid-exp": "無效的JSONata表達式: __error__", |         "invalid-exp": "無效的JSONata表達式: __error__", | ||||||
|         "msgprop": "資訊屬性", |         "msgprop": "資訊屬性", | ||||||
|         "msgobj": "完整資訊", |         "msgobj": "與調試輸出相同", | ||||||
|         "autostatus": "自動的", |         "autostatus": "自動的", | ||||||
|         "to": "目標", |         "to": "目標", | ||||||
|         "debtab": "除錯窗口", |         "debtab": "除錯窗口", | ||||||
|   | |||||||
| @@ -81,6 +81,7 @@ var api = module.exports = { | |||||||
|                     }) |                     }) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 if (!runtime.settings.disableEditor) { | ||||||
|                     safeSettings.context = runtime.nodes.listContextStores(); |                     safeSettings.context = runtime.nodes.listContextStores(); | ||||||
|  |  | ||||||
|                     if (util.isArray(runtime.settings.paletteCategories)) { |                     if (util.isArray(runtime.settings.paletteCategories)) { | ||||||
| @@ -112,8 +113,9 @@ var api = module.exports = { | |||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); |                     safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); | ||||||
|  |  | ||||||
|                     runtime.settings.exportNodeSettings(safeSettings); |                     runtime.settings.exportNodeSettings(safeSettings); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|                 resolve(safeSettings); |                 resolve(safeSettings); | ||||||
|             }catch(err) { |             }catch(err) { | ||||||
|   | |||||||
| @@ -51,6 +51,8 @@ function runGitCommand(args,cwd,env,emit) { | |||||||
|             err.code = "git_auth_failed"; |             err.code = "git_auth_failed"; | ||||||
|         } else if(/Permission denied \(publickey\)/i.test(stderr)) { |         } else if(/Permission denied \(publickey\)/i.test(stderr)) { | ||||||
|             err.code = "git_auth_failed"; |             err.code = "git_auth_failed"; | ||||||
|  |         } else if(/Authentication failed/i.test(stderr)) { | ||||||
|  |             err.code = "git_auth_failed"; | ||||||
|         } else if (/commit your changes or stash/i.test(stderr)) { |         } else if (/commit your changes or stash/i.test(stderr)) { | ||||||
|             err.code = "git_local_overwrite"; |             err.code = "git_local_overwrite"; | ||||||
|         } else if (/CONFLICT/.test(err.stdout)) { |         } else if (/CONFLICT/.test(err.stdout)) { | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ describe("api/admin/index", function() { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         before(function() { |         before(function() { | ||||||
|             app = adminApi.init({}); |             app = adminApi.init({},{}); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         beforeEach(function() { |         beforeEach(function() { | ||||||
|   | |||||||
							
								
								
									
										93
									
								
								test/unit/@node-red/editor-api/lib/admin/settings_spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								test/unit/@node-red/editor-api/lib/admin/settings_spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright JS Foundation and other contributors, http://js.foundation | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  * http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  **/ | ||||||
|  |  | ||||||
|  | var should = require("should"); | ||||||
|  | var request = require('supertest'); | ||||||
|  | var express = require('express'); | ||||||
|  | var bodyParser = require("body-parser"); | ||||||
|  | var sinon = require('sinon'); | ||||||
|  |  | ||||||
|  | var app; | ||||||
|  |  | ||||||
|  | var NR_TEST_UTILS = require("nr-test-utils"); | ||||||
|  |  | ||||||
|  | var info = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/settings"); | ||||||
|  | var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"); | ||||||
|  |  | ||||||
|  | describe("api/editor/settings", function() { | ||||||
|  |     before(function() { | ||||||
|  |         sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); | ||||||
|  |         app = express(); | ||||||
|  |         app.use(bodyParser.json()); | ||||||
|  |         app.get("/settings",info.runtimeSettings); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     after(function() { | ||||||
|  |         theme.settings.restore(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it('returns the runtime settings', function(done) { | ||||||
|  |         info.init({},{ | ||||||
|  |             settings: { | ||||||
|  |                 getRuntimeSettings: function(opts) { | ||||||
|  |                     return Promise.resolve({ | ||||||
|  |                         a:1, | ||||||
|  |                         b:2, | ||||||
|  |                         editorTheme: { existing: 789 } | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         request(app) | ||||||
|  |         .get("/settings") | ||||||
|  |         .expect(200) | ||||||
|  |         .end(function(err,res) { | ||||||
|  |             if (err) { | ||||||
|  |                 return done(err); | ||||||
|  |             } | ||||||
|  |             res.body.should.have.property("a",1); | ||||||
|  |             res.body.should.have.property("b",2); | ||||||
|  |             res.body.should.have.property("editorTheme",{existing: 789, test:456}); | ||||||
|  |             done(); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |     it('returns the runtime settings - disableEditor true', function(done) { | ||||||
|  |         info.init({disableEditor: true},{ | ||||||
|  |             settings: { | ||||||
|  |                 getRuntimeSettings: function(opts) { | ||||||
|  |                     return Promise.resolve({ | ||||||
|  |                         a:1, | ||||||
|  |                         b:2 | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         request(app) | ||||||
|  |         .get("/settings") | ||||||
|  |         .expect(200) | ||||||
|  |         .end(function(err,res) { | ||||||
|  |             if (err) { | ||||||
|  |                 return done(err); | ||||||
|  |             } | ||||||
|  |             res.body.should.have.property("a",1); | ||||||
|  |             res.body.should.have.property("b",2); | ||||||
|  |             // no editorTheme if disabledEditor true | ||||||
|  |             res.body.should.not.have.property("editorTheme"); | ||||||
|  |             done(); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | }); | ||||||
| @@ -155,6 +155,10 @@ describe("api/auth/users", function() { | |||||||
|        }); |        }); | ||||||
|  |  | ||||||
|         describe('#get',function() { |         describe('#get',function() { | ||||||
|  |             it("returns null for tokenHeader", function() { | ||||||
|  |                 should.not.exist(Users.tokenHeader()); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|             it('delegates get user',function(done) { |             it('delegates get user',function(done) { | ||||||
|                 Users.get('dave').then(function(user) { |                 Users.get('dave').then(function(user) { | ||||||
|                     try { |                     try { | ||||||
|   | |||||||
| @@ -32,7 +32,6 @@ describe("api/editor/settings", function() { | |||||||
|         sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); |         sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); | ||||||
|         app = express(); |         app = express(); | ||||||
|         app.use(bodyParser.json()); |         app.use(bodyParser.json()); | ||||||
|         app.get("/settings",info.runtimeSettings); |  | ||||||
|         app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings); |         app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings); | ||||||
|         app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings); |         app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings); | ||||||
|     }); |     }); | ||||||
| @@ -41,31 +40,6 @@ describe("api/editor/settings", function() { | |||||||
|         theme.settings.restore(); |         theme.settings.restore(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it('returns the runtime settings', function(done) { |  | ||||||
|         info.init({ |  | ||||||
|             settings: { |  | ||||||
|                 getRuntimeSettings: function(opts) { |  | ||||||
|                     return Promise.resolve({ |  | ||||||
|                         a:1, |  | ||||||
|                         b:2, |  | ||||||
|                         editorTheme: { existing: 789 } |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         request(app) |  | ||||||
|         .get("/settings") |  | ||||||
|         .expect(200) |  | ||||||
|         .end(function(err,res) { |  | ||||||
|             if (err) { |  | ||||||
|                 return done(err); |  | ||||||
|             } |  | ||||||
|             res.body.should.have.property("a",1); |  | ||||||
|             res.body.should.have.property("b",2); |  | ||||||
|             res.body.should.have.property("editorTheme",{existing: 789, test:456}); |  | ||||||
|             done(); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|     it('returns the user settings', function(done) { |     it('returns the user settings', function(done) { | ||||||
|         info.init({ |         info.init({ | ||||||
|             settings: { |             settings: { | ||||||
|   | |||||||
| @@ -101,7 +101,58 @@ describe("runtime-api/settings", function() { | |||||||
|                 result.user.should.not.have.property("private"); |                 result.user.should.not.have.property("private"); | ||||||
|             }) |             }) | ||||||
|         }); |         }); | ||||||
|  |         it("gets the filtered settings when editor disabled ", function() { | ||||||
|  |             settings.init({ | ||||||
|  |                 settings: { | ||||||
|  |                     disableEditor: true, | ||||||
|  |                     foo: 123, | ||||||
|  |                     httpNodeRoot: "testHttpNodeRoot", | ||||||
|  |                     version: "testVersion", | ||||||
|  |                     paletteCategories :["red","blue","green"], | ||||||
|  |                     exportNodeSettings: (obj) => { | ||||||
|  |                         obj.testNodeSetting = "helloWorld"; | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 nodes: { | ||||||
|  |                     listContextStores: () => { return {stores:["file","memory"], default: "file"} }, | ||||||
|  |                     paletteEditorEnabled: () => false, | ||||||
|  |                     getCredentialKeyType: () => "test-key-type" | ||||||
|  |                 }, | ||||||
|  |                 storage: { | ||||||
|  |                     projects: { | ||||||
|  |                         getActiveProject: () => 'test-active-project', | ||||||
|  |                         getFlowFilename:  () => 'test-flow-file', | ||||||
|  |                         getCredentialsFilename:  () => 'test-creds-file', | ||||||
|  |                         getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |             return settings.getRuntimeSettings({ | ||||||
|  |                 user: { | ||||||
|  |                     username: "nick", | ||||||
|  |                     anonymous: false, | ||||||
|  |                     image: "http://example.com", | ||||||
|  |                     permissions: "*", | ||||||
|  |                     private: "secret" | ||||||
|  |                 } | ||||||
|  |             }).then(result => { | ||||||
|  |                 result.should.have.property("user"); | ||||||
|  |                 result.user.should.have.property("username","nick"); | ||||||
|  |                 result.user.should.have.property("permissions","*"); | ||||||
|  |                 result.user.should.have.property("image","http://example.com"); | ||||||
|  |                 result.user.should.have.property("anonymous",false); | ||||||
|  |                 result.user.should.not.have.property("private"); | ||||||
|  |  | ||||||
|  |                 // Filtered out when disableEditor is true | ||||||
|  |                 result.should.not.have.property("paletteCategories",["red","blue","green"]); | ||||||
|  |                 result.should.not.have.property("testNodeSetting","helloWorld"); | ||||||
|  |                 result.should.not.have.property("foo",123); | ||||||
|  |                 result.should.not.have.property("flowEncryptionType","test-key-type"); | ||||||
|  |                 result.should.not.have.property("project"); | ||||||
|  |                 result.should.not.have.property("git"); | ||||||
|  |  | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|         it('includes project settings if projects available', function() { |         it('includes project settings if projects available', function() { | ||||||
|             settings.init({ |             settings.init({ | ||||||
|                 settings: { |                 settings: { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user