diff --git a/.github/ISSUE_TEMPLATE/--bug_report.md b/.github/ISSUE_TEMPLATE/--bug_report.md index 63923455e..7cdc9caad 100644 --- a/.github/ISSUE_TEMPLATE/--bug_report.md +++ b/.github/ISSUE_TEMPLATE/--bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Reproducable software issues in the core of Node-RED +about: Reproducible software issues in the core of Node-RED title: '' labels: '' assignees: '' diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 05fdacd51..92a0abb8a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -29,6 +29,6 @@ the [forum](https://discourse.nodered.org) or - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) -- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team. +- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team. - [ ] I have run `grunt` to verify the unit tests pass - [ ] I have added suitable unit tests to cover the new/changed functionality diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b75d30f..7ea3845c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,57 @@ +#### 1.0.4: Maintenance Release + +Runtime + + - Update all dependencies to latest fix versions + - Update JSONata to 1.8.1 + - #2473 Handle httpAdminRoot missing ending slash with login strategy Fixes + - #2470 Update https-proxy-agent + - #2461 Allow credentials to be provided as part of /flows api + - #2444 Move receive metric position to better reflect async changes Fixes + - #2406 Improve file store error when cache disabled and sync api used Closes + - #2399 cloneMessage should handle undefined without throwing err Fixes + - #2418 Fix the library api routes to prevent too broad matching of regex URLs + - #2417 Remove undefined loadFlowLibrary call + +Editor + + - #2465 Add better regex highlighting in jsonata edit mode Fixes + - Add regex awareness to jsonata formatter + - #2472 Avoid adding extra newlines when formating jsonata Fixes + - #2475 Add UI test case for error handling + - Avoid adding extra divs to edit form to avoid size miscalculation + - Upgrade to latest marked and dompurify libs + - Ensure catalog load errors are logged to the console + - #2460 Track context sidebar element paths to track formatting changes Fixes + - Battling Chrome Autocomplete, part 31: Wrap search input with form + - #2445 Trick chrome into autofilling dummy username/password inputs Fixes + - #2457 Fix garbled characters in library + - #2409 Filter palette using raw label not html formatted label Fixes + - #2400 Wrap long context values when displaying in sidebar Fixes + - Fix duplicating array item in visual json editor + - #2338 Modify history sidebar button positioning to handle long labels Fixes + - #2438 Add some auto-complete snippets to the nrjavascript mode Close + - #2430 Ignore disabled nodes when checking for invalid configs on deploy Closes + - #2442 #2458 #2453 Update zh-CN translations + - #2235 Add initial zh-TW translation + - Re-enable jshint on editor and fixup issues + - #2431 Remove unnecessary namespaces for i18n + - #2440 Support BrowserStack in UI testing + - #2358 Add path property to debug messages Fixes + - Fix false change detection when no config node selected + - Fix IME bug in text editor + - Make node highlighting a bit more obvious for busy flows + - #2392 Add icons and support i18n in typedInput of JSON editor + +Nodes + - #2462 MQTT: Ensure IPv6 broker names are wrapped in brackets Fixes + - Join node - check existance before clearing timeout + - Trigger: Complete 2nd msg when set to send latest + - TCP: clarify text regarding blank parameters. + - #2449 HTTP Request: Add HEAD as Method + - Make min-height for change, switch, batch and mqtt consistent + + #### 1.0.3: Maintenance Release Runtime @@ -44,7 +98,7 @@ Runtime Editor - Ensure node status is refreshed whenever node is edited - - #2315 #2316 Ensure z property included in full message debug payload + - #2316 Ensure z property included in full message debug payload #2315 - #2321 Fixed editor.json (JA nls) - #2313 Fix element to collapse items in visual JSON editor - #2314 Insert divider in menu by calling RED.menu.addItem('id', null); @@ -113,9 +167,9 @@ Editor - Move flow-status button to footer for consistency - Fix node hover effect to prevent jumping position - Filter quick-add properly when splicing a wire - - Mark workspace dirty when deleting link node link Fixes #2274 + - #2274 Mark workspace dirty when deleting link node link Fixes - Add red-ui-button class to strategy login button - - Fix padding of subflow locale select Closes #2276 + - #2276 Fix padding of subflow locale select Closes - Update info text of complete node & add JP text - Add class red-ui-button to cancel button - Add css class to login submit button (#2275) @@ -138,12 +192,12 @@ Runtime - [FEATURE] Add Node Done API - make message passing async - Ensure the subflow stop promise is waiting for before restarting - Limit the regex for the /nodes/ api end points - - Add error event handler to ssh-keygen child_process Fixes #2255 - - Fix default value handling on context array access Fixes #2252 + - #2255 Add error event handler to ssh-keygen child_process Fixes + - #2252 Fix default value handling on context array access Fixes - Remove all ui test dependencies from package.json - Add req back to audit log events and extend to Projects api - - Ensure 2nd arg to node.error is an object Fixes #2228 - - Use a more atomic process for writing context files Fixes #2271 + - #2228 Ensure 2nd arg to node.error is an object Fixes + - #2271 Use a more atomic process for writing context files Fixes Editor @@ -162,25 +216,25 @@ Editor - [FEATURE] add support for specifying subflow template color - [FEATURE] Use ctrl-click on wire to splice node in place - [FEATURE] Allow search results to show more than 25 results - - [FEATURE] Allow a node to change if it has an input port Closes #2268 - - Revealing node position needs to account for zoom level Fixes #2172 - - Fix typedInput option selection Fixes #2174 - - Fix palette node id handling so search works Fixes #2173 + - #2268 [FEATURE] Allow a node to change if it has an input port Closes + - #2172 Revealing node position needs to account for zoom level Fixes + - #2174 Fix typedInput option selection Fixes + - #2173 Fix palette node id handling so search works Fixes - Add popover tooltips to debug sidebar,function and template - Add popovers to context sidebar mini buttons - Ensure node status icon is shown when value set - Revert treeList children function signature change - Restore tray component css for compatibility. Mark as deprecated - fix function name & string compare function - - Handle empty list of example flows Fixes #2171 + - #2171 Handle empty list of example flows Fixes - Ensure library list has an item selected when opened - Ensure tooltip popover doesn't replace normal popover - Fix clipboard export download button - Ensure input box has focus on repeated quick add - Fix width calculation of typedInput - Remove some hardcoded css colors - - Fix display of node help when clicking in palette Fixes #2194 - - Ensure node help is loaded in the right language Fixes #2195 + - #2194 Fix display of node help when clicking in palette Fixes + - #2195 Ensure node help is loaded in the right language Fixes - Do not allow tab focus on clipboard hidden element - Fix undefined error on typedInput due to valueLabel used before being added - Fix undo of flow disable state change @@ -193,7 +247,7 @@ Editor - Update all node icons to SVG - Handle png/svg fallback for def.icon values. Remove old pngs - Ignore empty examples directories (don't add to import menu) - - better handle example file at any depth - #2222 + - #2222 better handle example file at any depth - - Properly escape node types in palette - Ensure session expiry timeout doesn't exceed limit - Use node/tab map to make filterNodes more efficient @@ -201,22 +255,22 @@ Editor - Handle undefined node.\_def in edit stack title. - fix converting selection to subflow - Fix inserting new subflow node to existing wire between nodes - - Support displaying falsey node status values Fixes #2246 + - #2246 Support displaying falsey node status values Fixes - Remove tab menu from node property UI for subflow and config nodes - - Mark workspace dirty when shift-click-drag detaches wires Fixes #2260 + - #2260 Mark workspace dirty when shift-click-drag detaches wires Fixes - Fix subflow category change on palette Nodes - Remove pi gpi, twitter, email and feedparser nodes from core - - Fix error handling in Websocket broadcast function Fixes #2182 + - #2182 Fix error handling in Websocket broadcast function Fixes - Handle websocket item being parseable but not an object better - stop join tripping up if last message of buffer is blank. - Add support for env var propety in switch node - Improve handling of file upload in request node - Add "has key" rule to switch node + tests - Optimise generation of switch node edit dialog - - Add keep-alive option to HTTP Request - #2261 + - #2261 Add keep-alive option to HTTP Request - #### 1.0.0-beta.2: Beta Release @@ -228,7 +282,7 @@ Runtime Runtime - Update runtime apis to support multiple libraries - Add Node 12 to travis (allow_failures) - - Bump all dependencies Fixes #2152 + - #2152 Bump all dependencies Fixes Editor - [BREAKING] complete overhaul of editor DOM/CSS structure @@ -240,17 +294,17 @@ Editor - Allow script tags with src to reference esm modules - Upgrade to jq 3.4.1 / jq-ui 1.12.1 - Allow editor language to be chosen in editor settings - - Only NLS status text that starts with a letter Fixes #2128 - - Fix display of link node list within subflow Fixes #2140 - - Blur the active element when closing edit dialog via action Fixes #2097 - - Trigger change evnt on typedInput when type changes and options present Fixes #2160 + - #2128 Only NLS status text that starts with a letter Fixes + - #2140 Fix display of link node list within subflow Fixes + - #2097 Blur the active element when closing edit dialog via action Fixes + - #2160 Trigger change evnt on typedInput when type changes and options present Fixes - Move library import/export to single dialog - Move type-library dialogs to new style dialog - Fix node drag and drop animation - let status be simple text if wanted - Add workspace statusBar - Complete refresh of German translations - - Fix memory leak in Debug sidebar #2163 + - #2163 Fix memory leak in Debug sidebar - Introduce toggleButton and move flow-disabled to use it - Allow RED.settings.get/set to use full property desc - Add auto-refresh toggle to context sidebar @@ -269,26 +323,26 @@ Nodes #### 0.20.8: Maintenance Release - Sanitize tab name in edit dialog - - Pass httpServer to runtime even when httpAdmin disabled Fixes #2272 + - #2272 Pass httpServer to runtime even when httpAdmin disabled Fixes #### 0.20.7: Maintenance Release - - Update jsonata to 1.6.5 which should fix #2183 + - #2183 Update jsonata to 1.6.5 which should fix - Ensure the subflow stop promise is waiting for before restarting - Properly escape node types in palette #### 0.20.6: Maintenance Release - - Revealing node position needs to account for zoom level Fixes #2172 + - #2172 Revealing node position needs to account for zoom level Fixes - stop join tripping up if last message of buffer is blank. - Improve handling of file upload in request node - - Handle subflow internal node wired to a non-existant node Fixes #2202 + - #2202 Handle subflow internal node wired to a non-existant node Fixes - Do not save subflow env vars with blank names - Don't allow a link node virtual wire to connect to normal port - - Clear HTTP Request node authType when auth disabled Fixes #2215 - - Fix parsing of content-type header Fixes #2216 + - #2215 Clear HTTP Request node authType when auth disabled Fixes + - #2216 Fix parsing of content-type header Fixes - Fix join node reset issue with merging objects - - Copy data-i18n attribute on TypedInput Fixes #2211 + - #2211 Copy data-i18n attribute on TypedInput Fixes #### 0.20.5: Maintenance Release @@ -325,27 +379,27 @@ Nodes #### 0.20.1: Maintenance Release - - Ensure all subflow instances are stopped when flow stopping Fixes #2095 - - modify name of korean locale forders #2091 + - #2095 Ensure all subflow instances are stopped when flow stopping Fixes + - #2091 modify name of korean locale forders - Ensure node names are sanitized before being presented - - Subflow status node must pass status to parent flow Fixes #2087 - - fix problem on displaying option label on Firefox #2090 + - #2087 Subflow status node must pass status to parent flow Fixes + - #2090 fix problem on displaying option label on Firefox #### 0.20.0: Milestone Release Runtime - Pass complete status to Status node and filter to editor - - Ensure flows wait for all nodes to close before restarting Fixes #2067 + - #2067 Ensure flows wait for all nodes to close before restarting Fixes - Fix git clone with password protected key - Allow a project to be located below the root of repo - Detect the cloning of an empty git repo properly - Fix use of custom auth strategy plugins - - Remove remnants of when library in git/index Fixes #2057 + - #2057 Remove remnants of when library in git/index Fixes - Clear subflow status on close - Add exportGlobalContextKeys to prevent exposing functionGlobalContext keys - Add --no-audit and --no-update-notifier flags to npm commands to reduce workload - Add envVarExcludes setting to block named env vars - - Update settings.js docs on userDir to match reality Fixes #2082 + - #2082 Update settings.js docs on userDir to match reality Fixes - Add Korean Language @@ -389,16 +443,16 @@ Editor - Add env type to subflow env var types - Display parent subflow properties in edit dialog - Fix direction value of subflow output - - Add Status Node to Subflow to allow subflow-specific status Closes #597 - - Better handling of multiple flow merges Fixes #2039 + - #597 Add Status Node to Subflow to allow subflow-specific status Closes + - #2039 Better handling of multiple flow merges Fixes Nodes - Various translation updates - - Catch: Add 'catch uncaught only' mode. Closes #1747 + - #1747 Catch: Add 'catch uncaught only' mode. Closes - Link: scroll to current flow in node list - HTTPRequest: add option to urlencode cookies - - HTTPRequest: option to use msg.payload as query params on GET. #1981 + - #1981 HTTPRequest: option to use msg.payload as query params on GET. - Debug: Add local time display option to numerics in debug window - MQTT: Add parsed JSON output option @@ -416,9 +470,9 @@ Editor - German translation - Change default dropdown appearance and sidebar tab menu handling - - Handle multiple-select box when nothing selected Fixes #2021 - - Handle i18n properly when key is a valid sub-identifier Fixes #2028 - - Avoid duplicate links when missing node type installed Fixes #2032 + - #2021 Handle multiple-select box when nothing selected Fixes + - #2028 Handle i18n properly when key is a valid sub-identifier Fixes + - #2032 Avoid duplicate links when missing node type installed Fixes - Add View Tools - Don't collapse version control header when clicking refresh - Add fast entry via keyboard for string of nodes @@ -438,15 +492,15 @@ Editor - Update palette manager view properly when module updated - Add TreeList common widget - - Fix visual jump when opening Comment editor on Safari Part of #2008 - - Fix vertical align of markdown editor in Safari Fixes #2008 - - Avoid marking node as changed if label state is default Fixes #2009 + - #2008 Fix visual jump when opening Comment editor on Safari Part of + - #2008 Fix vertical align of markdown editor in Safari Fixes + - #2009 Avoid marking node as changed if label state is default Fixes - Highlight port on node hover while joining - Support drag-wiring of link nodes - Allow TypeSearch to include a filter option - Improve diff colouring - Allow sections to toggle in 2-element stack - - Add support for ${} env var syntax when skipping validation Closes #1980 + - #1980 Add support for ${} env var syntax when skipping validation Closes - i18 support for markdown editor tooltip - Add RED.editor.registerTypeEditor for custom type editors - Tidy up markdown toolbar handling across all editors @@ -456,15 +510,15 @@ Editor Runtime - - Bump JSONata to 1.6.4 Fixes #2023 + - #2023 Bump JSONata to 1.6.4 Fixes - Add audit logging to admin api - - Fix failure of RED.require #2010 - - Allow oauth strategy callback method to be customised Closes #1998 - - Ensure fs context cache is flushed on close Fixes #2001 + - #2010 Fix failure of RED.require + - #1998 Allow oauth strategy callback method to be customised Closes + - #2001 Ensure fs context cache is flushed on close Fixes - Fix library Buffer( to Buffer.alloc( for node 10 - Catch file-not-found on startup when non-existant flow file specified - Actively expire login sesssions and notify user - - Add quotation marks for basic auth challenge #1976 + - #1976 Add quotation marks for basic auth challenge Nodes @@ -482,7 +536,7 @@ Nodes Editor - Allow the editor to use a custom admin api url root - - Improve performance of Flow Diff dialog - @TothiViseo #1989 + - #1989 Improve performance of Flow Diff dialog - @TothiViseo - Add 'open project' option to Projects Welcome dialog - Add 'type already registered' check in palette editor - Handle missing tab.disabled property @@ -500,11 +554,11 @@ Editor - Allow left-hand node button to act as toggle - Support dbl-click in tab bar to add new flow in position - Fix duplicate subflow detection on import - - Add import notification with info on what has been imported Closes #1862 + - #1862 Add import notification with info on what has been imported Closes - Show error details when trying to import invalid json - Show default icon when non-existent font-awesome icon was specified - Add configurable option for showing node label - - Avoid http redirects as Safari doesn't reuse Auth header Fixes #1903 + - #1903 Avoid http redirects as Safari doesn't reuse Auth header Fixes - Tidy up ace tooltip styling - Add event log to editor - Add tooltips to multiple editor elements @@ -532,30 +586,30 @@ Editor Runtime - Allow a project to be loaded from cmdline - - Handle lookup of undefined property in Global context Fixes #1978 + - #1978 Handle lookup of undefined property in Global context Fixes - Refuse to enable Manage Palette if npm too old - Remove restriction on upgrading non-local modules - - Remove deprecated Buffer constructor usage Fixes #1709 + - #1709 Remove deprecated Buffer constructor usage Fixes - Update httpServerOptions doc in settings.js - Exclude non-testable .js files from the unit tests - Add --safe mode flag to allow starting without flows running - - Add setting-defined accessToken for automated access to the adminAPI - #1789 + - #1789 Add setting-defined accessToken for automated access to the adminAPI - Nodes - - Move all core node EN help to their own locale files - #1990 + - #1990 Move all core node EN help to their own locale files - - CSV: better regex for number detection - Debug: hide button if not configured to send to sidebar - Delay: report queue activity when in by-topic mode - Delay: add msg.flush mode - Exec: Preserve existing properties on msg object - File: remove CR/LF from incoming filename - - Function: create custom ace javascript mode to handle ES6 Fixes #1911 + - #1911 Function: create custom ace javascript mode to handle ES6 Fixes - Function: add env.get - - HTTP Request: Add http-proxy config #1913 + - #1913 HTTP Request: Add http-proxy config - HTTP Request: add msg.redirectList to output - - HTTP Request: add msg.requestTimeout option for per-message setting - @natcl #1959 - - MQTT: add auto-detect and base64 output to mqtt node Fixes #1912 - @DurandA + - #1959 HTTP Request: add msg.requestTimeout option for per-message setting - @natcl + - #1912 - @DurandA MQTT: add auto-detect and base64 output to mqtt node Fixes - MQTT: only unsubscribe node that is being removed - Sentiment: move to node-red-node-sentiment - Switch: add missing edit dialog icon @@ -570,48 +624,48 @@ Nodes #### 0.19.6: Maintenance Release - - Fix encoding of file node from binary to utf8 - #2051 + - #2051 Fix encoding of file node from binary to utf8 - #### 0.19.5: Maintenance Release - Recognize pip installs of RPi.GPIO (#1934) - - Merge pull request #1941 from node-red-hitachi/master-batch - - Merge pull request #1931 from node-red-hitachi/master-typedinput + - #1941 from node-red-hitachi/master-batch Merge pull request + - #1931 from node-red-hitachi/master-typedinput Merge pull request - Set min value of properties and spinners for batch - Fix that unnecessary optionMenu remains - - Merge pull request #1894 from node-red-hitachi/fix-overlapping-file-node-execution - - Merge pull request #1924 from imZack/patch-1 + - #1894 from node-red-hitachi/fix-overlapping-file-node-execution Merge pull request + - #1924 from imZack/patch-1 Merge pull request - Add missing comma - - Do not disable context sidebar during node edit Fixes #1921 - - Don't allow virtual links to be spliced Fixes #1920 + - #1921 Do not disable context sidebar during node edit Fixes + - #1920 Don't allow virtual links to be spliced Fixes - Merge project package changes to avoid overwritten changes - - Handle manually added project deps that are unused Fixes #1908 + - #1908 Handle manually added project deps that are unused Fixes - update close & input handling of File node - make close handler argument only one - - Merge pull request #1907 from amilajack/patch-2 + - #1907 from amilajack/patch-2 Merge pull request - Change repo badge to point to master branch - invoke callbacks if async handler is specified - - Merge pull request #1891 from camlow325/resolve-example-path-for-windows-support - - Merge pull request #1900 from kazuhitoyokoi/master-addtestcases4settings.js + - #1891 from camlow325/resolve-example-path-for-windows-support Merge pull request + - #1900 from kazuhitoyokoi/master-addtestcases4settings.js Merge pull request - wait closing while pending messages exist - Add test cases for red/api/editor/settings.js - - Ensure all palette categories are opened properly Closes #1893 + - #1893 Ensure all palette categories are opened properly Closes - Resolve path when sending example file for Windows support - fix multiple input message processing of file node #### 0.19.4: Maintenance Release - - Fix race condition in non-cache lfs context Fixes #1888 + - #1888 Fix race condition in non-cache lfs context Fixes - LocalFileSystem Context: Remove extra flush code - Prevent race condition in caching mode of lfs context (#1889) - Allow context store name to be provided in the key - Switch node: only use promises when absolutely necessary - Fix dbl-click handling on webkit-based browsers - Ensure context.flow/global cannot be deleted or enumerated - - Handle context.get with multiple levels of unknown key Fixes #1883 + - #1883 Handle context.get with multiple levels of unknown key Fixes - Fix global.get("foo.bar") for functionGlobalContext set values - Fix node color bug (#1877) - - Merge pull request #1857 from cclauss/patch-1 + - #1857 from cclauss/patch-1 Merge pull request - Define raw_input() in Python 3 & fix time.sleep() #### 0.19.3: Maintenance Release @@ -626,14 +680,14 @@ Nodes - TCP-request node - only write payload - JSON schema: perform validation when obj -> obj or str -> str - JSON schema: add draft-06 support (via $schema keyword) - - Mqtt proxy configuration for websocket connection, #1651. + - #1651. Mqtt proxy configuration for websocket connection, - Allows MQTT Shared Subscriptions for MQTT-In core node - Fix use of HTML tag or CSS class specification as icon of typedInput #### 0.19.2: Maintenance Release - Ensure node default colour is used if palette.theme has no match - - fix lost messages / properties in TCPRequest Node; closes #1863 (#1864) + - #1863 (#1864) fix lost messages / properties in TCPRequest Node; closes - Fix typo in template.html - Improve error reporting from context plugin loading - Prevent no-op edit of node marking as changed due to icon @@ -653,8 +707,8 @@ Nodes Editor - Add editorTheme.palette.theme to allow overriding colours - - Index all node properties when searching Fixes #1446 - - Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779 + - #1446 Index all node properties when searching Fixes + - #1779 Handle NaN and Infinity properly in debug sidebar Fixes #1778 - Prevent horizontal scroll when palette name cannot wrap - Ignore middle-click on node/ports to enable panning - Better wire layout when looping back @@ -668,7 +722,7 @@ Editor - Only edit nodes on dbl click on primary button with no modifiers - Allow subflows to be put in any palette category - Add flow navigator widget - - Cache flow library result to improve response time Fixes #1753 + - #1753 Cache flow library result to improve response time Fixes - Add middle-button-drag to pan the workspace - allow multi-line category name in editor - Redesign sidebar tabs @@ -676,20 +730,20 @@ Editor Nodes - - Change: Ensure runtime errors in Change node can be caught Fixes #1769 + - #1769 Change: Ensure runtime errors in Change node can be caught Fixes - File: Add output to File Out node - Function: add expandable JavaScript editor pane - Function: allow id and name reference in function node code (#1731) - HTTP Request: Move to request module - - HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278 + - #1278 HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes - Join: accumulate top level properties - Join: allow environment variable as reduce init value - JSON: add JSON schema validation via msg.schema - Pi: Let nrgpio code work with python 3 - Pi: let Pi nodes be visible/editable on all platforms - Switch: add isEmpty rule - - TCP: queue messages while connecting; closes #1414 - - TLS: Add servername option to TLS config node for SNI Fixes #1805 + - #1414 TCP: queue messages while connecting; closes + - #1805 TLS: Add servername option to TLS config node for SNI Fixes - UDP: Don't accidentally re-use udp port when set to not do so Persistent Context @@ -704,8 +758,8 @@ Persistent Context Runtime - Support flow.disabled and .info in /flow API - - Node errors should be Strings not Errors Fixes #1781 - - Add detection of connection timeout in git communication Fixes #1770 + - #1781 Node errors should be Strings not Errors Fixes + - #1770 Add detection of connection timeout in git communication Fixes - Handle loading empty nodesDir - Add 'private' property to userDir generated package.json - Add RED.require to allow nodes to access other modules @@ -715,7 +769,7 @@ Runtime Editor Fixes - - Do not trim wires if node declares outputs in defaults but misses value Fixes #1737 + - #1737 Do not trim wires if node declares outputs in defaults but misses value Fixes Node Fixes @@ -725,16 +779,16 @@ Node Fixes - typo fix in node help (#1735) Other Fixes - - Tidy up default grunt task and fixup test break due to reorder Fixes #1738 + - #1738 Tidy up default grunt task and fixup test break due to reorder Fixes - Bump jsonata version #### 0.18.6: Maintenance Release Editor Fixes - - Handle a node having wires in the editor on ports it no longer has Fixes #1724 + - #1724 Handle a node having wires in the editor on ports it no longer has Fixes - Add missing ACE snippet files - - Fix wireClippedNodes is not defined Fixes #1726 + - #1726 Fix wireClippedNodes is not defined Fixes - Split node html to isolate bad nodes when loading - Avoid unnecessary use of .html() where .text() will do @@ -761,32 +815,32 @@ New Features Editor Fixes - - Highlight subflow node when log msg comes from inside Fixes #1698 - - Ensure node wires array is not longer than outputs value Fixes #1678 - - Allow importing an unknown config node to be undone Fixes #1681 - - Ensure keyboard shortcuts get saved in runtime settings Fixes #1696 + - #1698 Highlight subflow node when log msg comes from inside Fixes + - #1678 Ensure node wires array is not longer than outputs value Fixes + - #1681 Allow importing an unknown config node to be undone Fixes + - #1696 Ensure keyboard shortcuts get saved in runtime settings Fixes - Don't mark a subflow changed when actually modified nothing (#1665) Node Fixes - bind to correct port when doing udp broadcast/multicast (#1686) - Provide full error stack in Function node log message (#1700) - - Fix http request doc type Fixes #1690 + - #1690 Fix http request doc type Fixes - Make debug slightly larger to pass WCAG AA rating - - Make core nodes labels more consistent, to close #1673 - - Allow template node to be updated more than once Fixes #1671 + - #1673 Make core nodes labels more consistent, to close + - #1671 Allow template node to be updated more than once Fixes - Fix the problem that output labels of switch node sometimes disappear (#1664) - Chinese translations for core nodes (#1607) Runtime Fixes - - Handle and display for invalid flow credentials when project is disabled #1689 (#1694) + - #1689 (#1694) Handle and display for invalid flow credentials when project is disabled - node-red-pi: fix behavior with old bash version (#1713) - Fix ENOENT error on first start when no user dir (#1711) - - Handle null error object in Flow.handleError Fixes #1721 + - #1721 Handle null error object in Flow.handleError Fixes - update settings comments to describe how to setup for ipv6 (#1675) - - Remove credential props after diffing flow to prevent future false positives Fixes #1359 - - Log error if settings unavailable when saving user settings Fixes #1645 + - #1359 Remove credential props after diffing flow to prevent future false positives Fixes + - #1645 Log error if settings unavailable when saving user settings Fixes - Keep backup of .config.json - Add warning if using \_credentialSecret from .config.json - Filter req.user in /settings to prevent potentially leaking info @@ -812,7 +866,7 @@ Editor Fixes - Fix merging a remote diff - Fixed the problems when using a node without defaults - Disable user defined icon for subflow - - getDefaultNodeIcon should handle subflow instance nodes Fixes #1635 + - #1635 getDefaultNodeIcon should handle subflow instance nodes Fixes - Add Japanese info text for core nodes - Fix message lookup for core nodes in case of i18 locales directory exists - Prevent the last tab from being deleted @@ -843,7 +897,7 @@ Editor Fixes - Fix offset calculation when dragging node from palette - Allow a library entry to use non-default node-input- prefixes - - Change remote-diff shortcut and add it to keymap Fixes #1628 + - #1628 Change remote-diff shortcut and add it to keymap Fixes #### 0.18.2: Maintenance Release @@ -882,7 +936,7 @@ Projects - Handle more repo clone error cases - Relax validation of git urls - Revalidate project name on return to project-details view - - Avoid unnecessary project refresh on branch-switch Fixes #1597 + - #1597 Avoid unnecessary project refresh on branch-switch Fixes - Add support for file:// git urls - Handle project first-run without existing flow file - Handle delete of last remote in project settings @@ -893,9 +947,9 @@ Projects Node Fixes - Trigger node migration - ensure bytopic not blank - - Add HEAD to list of methods with no body in http req node #1598 - - Do not include payload in GET requests Fixes #1598 - - Update sort/batch docs Fixes #1601 + - #1598 Add HEAD to list of methods with no body in http req node + - #1598 Do not include payload in GET requests Fixes + - #1601 Update sort/batch docs Fixes - Don't assume node has defaults when exporting icon property @@ -908,11 +962,11 @@ Runtime - Better error reporting when module provides duplicate type - Update jsonata to 1.5.0 - add express-session memorystore without leaks (#1435) - - Allow adminAuth.user to be a Function Fixes #1461 + - #1461 Allow adminAuth.user to be a Function Fixes - Ensure RED.server is set even if admin api disabled - - Ensure strategy login button uses relative URL Fixes #1481 + - #1481 Ensure strategy login button uses relative URL Fixes - ignore `_msgid` when merging full objects - - Move node install to spawn to allow for big stdout Fixes #1488 + - #1488 Move node install to spawn to allow for big stdout Fixes - SIGINT handler should wait for stop to complete before exit Editor @@ -920,12 +974,12 @@ Editor - allow a node's icon to be set dynamically (#1490) - Batch messages sent over comms to increase throughput - Migrate deploy confirmations to notifications - - `oneditdelete` should be available to all node types Closes #1346 + - #1346 `oneditdelete` should be available to all node types Closes - Sort typeSearch results based on position of match - Update ACE to test and add python highlighter (#1373) - - Clear mouse state when typeSearch cancelled Fixes #1517 + - #1517 Clear mouse state when typeSearch cancelled Fixes - Handle scoped modules via palette editor - - TypedInput: handle user defined value/labels options Fixes #1549 + - #1549 TypedInput: handle user defined value/labels options Fixes Nodes @@ -940,7 +994,7 @@ Nodes - Add support for rejectUnauthorized msg property - Add TLS options to WebSocket client - Added parsed YAML support for template node (#1443) - - Allow delay node in rate-limit mode to be reset Fixes #1360 + - #1360 Allow delay node in rate-limit mode to be reset Fixes - Allow setTimeout in Function node to be promisified in node 8 - Debug to status option (#1499) - enable template config via msg.template for stored or generated templates (#1503) @@ -959,13 +1013,13 @@ Nodes - MQTT node - if Server/URL config contains '//' use it as a complete url; enabled ws:// and wss:// - clone messages before delayed send (#1474) - Decrement connected client count rather than show disconnected - - Don't end mqtt client on first error Fixes #1566 - - File out - create dirs synchronously to ensure they exist Fixes #1489 + - #1566 Don't end mqtt client on first error Fixes + - #1489 File out - create dirs synchronously to ensure they exist Fixes - Fix debug message format for Buffer (#1444) - Fix global.keys() bug in function node (#1417) - Handle escape characters in template node which uses Mustache format and JSON output mode (#1377) - - Move all node.send to end of timer functions in trigger node (issue #1527) (#1539) - - Publish null/undefined to mqtt as blank not toString Fixes #1521 + - #1527) (#1539) Move all node.send to end of timer functions in trigger node (issue + - #1521 Publish null/undefined to mqtt as blank not toString Fixes - remove inject node at specific time spinner - restrict inject interval to less that 2^31 millisecs - tag UDP ports in use properly so they get closed correctly (#1508) @@ -974,10 +1028,10 @@ Nodes - Add express-session missing dependency for oauth - Fix improper type tests is core test cases - - File node: recreate write stream when file deleted Fixes #1351 + - #1351 File node: recreate write stream when file deleted Fixes - Add flow stopping trace messages - Fix userDir test case when .config.json exists (#1350) - - Do not try to send msg after http request error handled Fixes #1344 + - #1344 Do not try to send msg after http request error handled Fixes - Fix boundary problem in range node (#1338) - Modify messages in node properties to refer messages.json (#1339) - Fix settings.js replacing webSocketVerifyClient by webSocketNodeVerifyClient (#1343) @@ -988,16 +1042,16 @@ Nodes - Add request node test case for POSTing 0 - Allow false and 0 in payload for httprequest (#1334) - Add file extension into flow name of library automatically (#1331) - - Fix accessing global context from jsonata expressions Fixes #1335 - - Disable editor whilst a deploy is inflight Fixes #1332 + - #1335 Fix accessing global context from jsonata expressions Fixes + - #1332 Disable editor whilst a deploy is inflight Fixes - Replace Unknown nodes with their real versions when node loaded - Retry auto-install of modules that fail - Fix column name in link nodes to refer language file (#1330) - - Use namespaces with link node title attributes i18n name Fixes #1329 - - Tidy up GPIO pin table presentation Fixes #1328 + - #1329 Use namespaces with link node title attributes i18n name Fixes + - #1328 Tidy up GPIO pin table presentation Fixes - Join: count of 0 should not send on every msg - Handle importing only one end of a link node pair - - Make sending to Debug synchronous again Fixes #1323 + - #1323 Make sending to Debug synchronous again Fixes - Make send-error behaviour optional in file node - Restore File In node behaviour of sending msg on error - Expose context.keys within Function node @@ -1011,10 +1065,10 @@ Nodes - Fix missing icons for some nodes (#1321) - Add reformat button to JSONata test data editor - Update delay node status without spawning unnecessary intervals - - Avoid stringify ServerResponse and Socket in Debug node Fixes #1311 + - #1311 Avoid stringify ServerResponse and Socket in Debug node Fixes - Fix creating userDir other than system drive on Windows (#1317) - - Trigger node not handling a duration of 0 as block mode Fixes #1316 - - Unable to config GPIO Pin 13 Fixes #1314 + - #1316 Trigger node not handling a duration of 0 as block mode Fixes + - #1314 Unable to config GPIO Pin 13 Fixes #### 0.17.2: Maintenance Release @@ -1023,7 +1077,7 @@ Nodes #### 0.17.1: Maintenance Release - Fix PI gpio to use BCM - - Prevent event thread contention when sending to Debug node Closes #1311 + - #1311 Prevent event thread contention when sending to Debug node Closes - Fix Bug: Can not display node icon when npm package has scope (#1305) (#1309) - Clear moved flag when nodes are deployed @@ -1031,7 +1085,7 @@ Nodes Runtime - - Return flow rev on reload api when api v2 enabled Closes #1273 + - #1273 Return flow rev on reload api when api v2 enabled Closes - Provide single endpoint to load all node message catalogs - Add .trace and .debug to Node prototype - Rename oauth auth scheme to strategy as it works for openid @@ -1039,16 +1093,16 @@ Runtime - Add support for oauth adminAuth configs - Cache auth details to save needlessly recalculating hashes - Add context.keys function to list top-level keys - - Strip BOM character from JSON files if present Fixes #1239 + - #1239 Strip BOM character from JSON files if present Fixes - Version check no meta (#1243) - - Ensure all nodes have access to global context Fixes #1230 - - Don't process subscription for unauthenticated comms link Fixes #851 - - Clone credentials when passing to node Fixes #1198 + - #1230 Ensure all nodes have access to global context Fixes + - #851 Don't process subscription for unauthenticated comms link Fixes + - #1198 Clone credentials when passing to node Fixes - Resolve dir argument of getLocalNodeFiles function (#1216) - Add wait for writing a library entry into a file. (#1186) - Use correct Buffer.from method rather than constructor - update core nodes to use newer Buffer syntax - - Treat missing msg properties as undefined rather than throw error Fixes #1167 + - #1167 Treat missing msg properties as undefined rather than throw error Fixes - Allows flows to be enabled/disabled in the runtime - add off option to logging settings comment - Log error stack traces if verbose flag is set @@ -1081,8 +1135,8 @@ Nodes - Fix wrong number of double quotes in CSV parsing - let csv node handle ip addresses without trying to parse - Update debug node to register the settings it uses - - Handle IncomingMessage/ServerResponse object types in debug Fixes #1202 - - Toggling debug node enabled/disabled state should set state dirty Fixes #1203 + - #1202 Handle IncomingMessage/ServerResponse object types in debug Fixes + - #1203 Toggling debug node enabled/disabled state should set state dirty Fixes - redo delay node status messages to be interval based - Update delay node ui - Add new msg.delay option to delay node @@ -1118,16 +1172,16 @@ Nodes - First pass of new node-info style - MQTT new style info - Fix empty extra node help content issue - - Handle HTTP In url that is missing its leading / Fixes #1218 + - #1218 Handle HTTP In url that is missing its leading / Fixes - Add file upload support to HTTP In node - HTTP Request node: add info on how to do form encoding - - Prevent unmodified msg.headers from breaking HTTP Request flows Closed #1015 + - #1015 Prevent unmodified msg.headers from breaking HTTP Request flows Closed - Add cookie handling to HTTP Request node - Add guard against the http-request buffer fix being reverted - Multipart streaming - Add http-request node unit tests - http request node add transport validity check and warn. - - Update follow_redirects to fix http_proxy handling Fixes #1172 + - #1172 Update follow_redirects to fix http_proxy handling Fixes - Allow statusCode/headers to be set directly within HTTP Response node - let inject "between time" also fire at start - Plus new info - remove repeat symbol from inject if repeat is 0 @@ -1176,7 +1230,7 @@ Nodes - Move udp sock error listener to only be instantiated once. - Let watch node recurse into subdirectories - Misconfigured WebSocket nodes should not register msg handlers - - Add websocketVerifyClient option to enable custom websocket auth Fixes #1127 + - #1127 Add websocketVerifyClient option to enable custom websocket auth Fixes Editor @@ -1198,11 +1252,11 @@ Editor - Remove unused modified flag on debug messages - Add copy path/value buttons to debug messages - dont match only part of the node type (#1242) - - Add editorTheme.logout.redirect to allow redirect on logout Closes #1213 - - Handle logging out and already logged-out editor Fixes #1288 + - #1213 Add editorTheme.logout.redirect to allow redirect on logout Closes + - #1288 Handle logging out and already logged-out editor Fixes - Fix bug: Export Subflows (#1282) - destroy editor to ensure fully removed on close (function, template, comment) - - Don't try to nls status text starting with '.' Fixes #1258 + - #1258 Don't try to nls status text starting with '.' Fixes - Add note of removed flows in diffConfig (#1253) - Add description to flow same as subflow - Allow tabs to be enabled/disabled in the editor @@ -1274,7 +1328,7 @@ Editor - Allow RED.validators.number to allow blank values as valid - Support dropping json files into the editor - NLS Expression/JSON editor and fix their height calculation - - Update JSONata to 1.2.4 Closes #1275 + - #1275 Update JSONata to 1.2.4 Closes - Remember test expression data on a per-node basis - NLS jsonata test messages - Add JSONata expr tester and improved feedback @@ -1291,13 +1345,13 @@ Other #### 0.16.2: Maintenance Release - - Ensure custom mustache context parent set in Template node fixes #1126 + - #1126 Ensure custom mustache context parent set in Template node fixes - Display debug node name in debug panel if its known - Ensure auth-tokens are removed when no user is specified in settings - Ensure all a tags have blank target in info sidebar - Ensure links do not span tabs in the editor - Avoid creating multiple reconnect timers in websocket node - - Fix inner reference in install fail message catalog entry Fixes #1120 + - #1120 Fix inner reference in install fail message catalog entry Fixes - Display buffer data properly for truncated buffers under Object property #### 0.16.1: Maintenance Release @@ -1305,9 +1359,9 @@ Other - Add colour swatches to debug when hex colour matched - Nodes with hasUsers set to false should not appear unused - Change hard error to verbose warning if using old node.js level - - Don't filter debug properties starting with _ Fixes #1117 - - Node logged errors not displayed properly in debug pane Fixes #1116 - - Do not look for existing nodes when checking for wires on paste Fixes #1114 + - #1117 Don't filter debug properties starting with _ Fixes + - #1116 Node logged errors not displayed properly in debug pane Fixes + - #1114 Do not look for existing nodes when checking for wires on paste Fixes - -v option not enabling verbose mode properly - Add node.js version check on startup @@ -1319,10 +1373,10 @@ Runtime Nodes - - Add option to colourise debug console output Closes #1103 + - #1103 Add option to colourise debug console output Closes - Add property validation to nodes using typedInput - - Add common validator for typedInput fields Closes #1104 - - Update debug node console logging indicator icon Closes #1094 + - #1104 Add common validator for typedInput fields Closes + - #1094 Update debug node console logging indicator icon Closes - Let exec node (spawn) handle commands with spaces in path - Add symbol to debug node to indicate debugging also to console.log - Change file node to use node 4 syntax (drops support for 0.8) @@ -1334,9 +1388,9 @@ Nodes Editor - - Add install/remove dialog to increase friction Closes #1109 - - Report node catalogue load errors Closes #1009 - - Properly report module remove errors in palette editor Fixes #1043 + - #1109 Add install/remove dialog to increase friction Closes + - #1009 Report node catalogue load errors Closes + - #1043 Properly report module remove errors in palette editor Fixes - Update rather than hide install button after success install - Tweak search box styling - Display info tips slightly longer @@ -1357,36 +1411,36 @@ Editor - Focus tray body when edit dialog opened - Hit enter to edit first node in selection - Add node delete button to edit dialog - - Add notification when runtime stopped due to missing types Part of #832 + - #832 Add notification when runtime stopped due to missing types Part of Fixes - - Do not tie debug src loading to needsPermission Fixes #1111 - - Initialise nodeApp regardless of httpAdmin setting Closes #1096 #1095 + - #1111 Do not tie debug src loading to needsPermission Fixes + - #1095 Initialise nodeApp regardless of httpAdmin setting Closes #1096 - Speed up reveal of search dialogs - - Ensure flows exist before delegating status/error events Fixes #1069 + - #1069 Ensure flows exist before delegating status/error events Fixes - Update package dependencies - Update MQTT to latest 2.2.1 - Node status not being refreshed properly in the editor - - Try to prevent auto-fill of password fields in node edit tray Fixes #1081 + - #1081 Try to prevent auto-fill of password fields in node edit tray Fixes - Fix whitespace in localfilesystem - fix bug where savesettings did not honor local settings variables (#1073) - - Tidy up unused/duplicate editor messages Closes #922 + - #922 Tidy up unused/duplicate editor messages Closes - Property expressions must not be blank - Tidy up merge commit of validatePropertyExpression - add port if wires array > number of ports declared. - - Allow quoted property expressions Fixes #1101 + - #1101 Allow quoted property expressions Fixes - Index all node properties for node search - Remove node 0.10 from travis config - update welcome message to use logger so it can be turned off/on if required (#1083) - Fix dynamically loading multiple node-sets from palette editor - - Allow a node to reorder its outputs and maintain links Fixes #1031 + - #1031 Allow a node to reorder its outputs and maintain links Fixes #### 0.15.3: Maintenance Release - Tcpgetfix: Another small check (#1070) - TCPGet: Ensure done() is called only once (#1068) - - Allow $ and _ at start of property identifiers Fixes #1063 + - #1063 Allow $ and _ at start of property identifiers Fixes - TCPGet: Separated the node.connected property for each instance (#1062) - Corrected 'overide' typo in XML node help (#1061) - TCPGet: Last property check (hopefully) (#1059) @@ -1419,11 +1473,11 @@ Fixes #### 0.15.2: Maintenance Release - - Revert bidi changes to nodes and hide menu option until fixed Fixes #1024 + - #1024 Revert bidi changes to nodes and hide menu option until fixed Fixes - Let xml node set options both ways - Bump serialport to use version 4 - gpio node handle multiple bits of data returned in one go - - HTTP In should pass application/octet-stream as buffer not string Fixes #1023 + - #1023 HTTP In should pass application/octet-stream as buffer not string Fixes - Handle missing httpNodeRoot setting properly - Config sidebar not handling node definition error properly - Add minimum show time to deploy spinner to avoid flicker @@ -1431,24 +1485,24 @@ Fixes - Add log.removeHandler function - Add Crtl/Shift/p shortcut for manage palette - Add spinner to deploy button - - Status messages from nodes in subflows not delegated properly Fixes #1016 + - #1016 Status messages from nodes in subflows not delegated properly Fixes - fix spelling in join node info - Speed up tab scrolling - - Update delay burst test to be more tolerant of timing Fixes #1013 + - #1013 Update delay burst test to be more tolerant of timing Fixes #### 0.15.1: Maintenance Release - Update default palette catalogue to use https - Disable palette editor if npm not found - and fix for Windows - - Searching package catalogue should be case-insensitive Fixes #1010 - - contenteditable fields not handled in config nodes Fixes #1011 + - #1010 Searching package catalogue should be case-insensitive Fixes + - #1011 contenteditable fields not handled in config nodes Fixes - Change html link refs from `_new` to `_blank` to be standards compliant #### 0.15.0: Milestone Release Runtime - - Increase default apiMaxLength to 5mb and add to default settings Closes #1001 + - #1001 Increase default apiMaxLength to 5mb and add to default settings Closes - Add v2 /flows api and deploy-overwrite protection - Encrypt credentials by default - Ensure errors thrown by RED.events handlers don't percolate up @@ -1457,7 +1511,7 @@ Editor - Mark nodes as changed when they are moved - Added parent containment option for draggable. (#1006) - - Ignore bidi event handling on non-existent and non-Input elements Closes #999 + - #999 Ignore bidi event handling on non-existent and non-Input elements Closes - Remove list of flows from menu - Allow nodes to be imported with their credentials - Add workspace search option @@ -1469,7 +1523,7 @@ Editor - Add import-to-new-tab option - Add new options to export-nodes dialog - Stop nodes being added beyond the outer bounds of the workspace - - Default config nodes to global scope unless in a subflow Closes #972 + - #972 Default config nodes to global scope unless in a subflow Closes - Bidi support for Text Direction and Structured Text (#961) - Fix jQuery selector, selecting more than one help pane/popover and displaying incorrectly. (#970) - Fixes removeItem not passing row data to callback. (#965) @@ -1483,7 +1537,7 @@ Nodes - Clean up status on close for several core nodes. - Change node: re-parse JSON set value each time to avoid pass-by-ref - Better handle HTTP Request header capitalisation - - Enable ES6 parsing in Function editor by default Fixes #985 + - #985 Enable ES6 parsing in Function editor by default Fixes - Update debug sidebar to use RED.view.reveal to show debug nodes - Add full path tip to file node, And tidy up Pi node tips - Remove WebSocket node maxlistener warning @@ -1502,7 +1556,7 @@ Nodes Other - - Add npm build/test scripts Closes #946 #660 + - #660 Add npm build/test scripts Closes #946 - Move travis to node 6 and 7 - drop 5 and 0.12 @@ -1510,15 +1564,15 @@ Other Fixes - - Tell ace about Function node globals. Closes #927 - - Tidy up mqtt nodes - linting and done handling. Closes #935 + - #927 Tell ace about Function node globals. Closes + - #935 Tidy up mqtt nodes - linting and done handling. Closes - Fix invalid html in TCP and HTML node edit templates - Add proper help text to link nodes - Handle importing old mqtt-broker configs that lack properties - Update ace to 1.2.4 - Allow config nodes to provide a sort function for their select list - Add log warning if node module required version cannot be satisfied - - Handle empty credentials file. Closes #937 + - #937 Handle empty credentials file. Closes - Add RPi.GPIO lib test for ArchLinux #### 0.14.5: Maintenance Release @@ -1528,8 +1582,8 @@ Fixes - Cannot clear cookies with http nodes - let HTML parse node allow msg.select set select - Validate nodes on import after any references have been remapped - - Debug node handles objects without constructor property Fixes #933 - - Ensure 'false' property values are displayed in info panel Fixes #940 + - #933 Debug node handles objects without constructor property Fixes + - #940 Ensure 'false' property values are displayed in info panel Fixes - Fix node enable/disable over restart - load configs after settings init #### 0.14.4: Maintenance Release @@ -1538,20 +1592,20 @@ Nodes - Update trigger node ui to use typedInputs - Better handling of quotes in CSV node - - Clarify the MQTT node sends msg.payload - closes #929 - - Inject node should reuse the message it is triggered with Closes #914 + - #929 Clarify the MQTT node sends msg.payload - closes + - #914 Inject node should reuse the message it is triggered with Closes - Stop trigger node re-using old message - Allow node.status text to be 'falsey' values Fixes - - Handle DOMException when embedded in an iframe of different origin Fixes #932 + - #932 Handle DOMException when embedded in an iframe of different origin Fixes - Fix double firing of menu actions - - Fix select box handling in Safari - fixes #928 - - Clear context in node test helper Fixes #858 - - Allow node properties to be same as existing object functions Fixes #880 + - #928 Fix select box handling in Safari - fixes + - #858 Clear context in node test helper Fixes + - #880 Allow node properties to be same as existing object functions Fixes - Handle comms link closing whilst completing the initial connect - - Protect against node type names that clash with Object property names Fixes #917 + - #917 Protect against node type names that clash with Object property names Fixes - Clone default node properties to avoid reference leakage - Strip tab node definition when exporting - Check for null config properties in editor before over-writing them @@ -1561,16 +1615,16 @@ Editor - Add sql mode to ace editor - Keyboard shortcuts dialog update (#923) - - Ensure importing link nodes to a subflow doesn't add outbound links Fixes #921 + - #921 Ensure importing link nodes to a subflow doesn't add outbound links Fixes - Add updateConfigNodeUsers function to editor - Scroll to bottom when item added to editableList - - Form input widths behave more consistently when resizing Fixes #919 #920 + - #920 Form input widths behave more consistently when resizing Fixes #919 #### 0.14.3: Maintenance Release Fixes - - Create default setting.js in user-specified directory. Fixes #908 + - #908 Create default setting.js in user-specified directory. Fixes - MQTT In subscription qos not defaulting properly - Let exec node handle 0 as well as "0" @@ -1578,7 +1632,7 @@ Fixes Fixes - - Cannot add new twitter credentials. Fixes #913 + - #913 Cannot add new twitter credentials. Fixes - Support array references in Debug property field #### 0.14.1: Maintenance Release @@ -1586,8 +1640,8 @@ Fixes Fixes - Handle undefined property that led to missing wires in the editor - - Remove duplicate 'Delete' entry in keyboard shortcut window. Closes #911 - - Add 'exec' to node-red-pi launch script. Closes #910 + - #911 Remove duplicate 'Delete' entry in keyboard shortcut window. Closes + - #910 Add 'exec' to node-red-pi launch script. Closes #### 0.14.0: Milestone Release @@ -1609,9 +1663,9 @@ Editor Runtime - Always log node warnings on start without requiring -v - - Add support for loading scoped node modules. Closes #885 + - #885 Add support for loading scoped node modules. Closes - Add process.env.PORT to settings.js - - Clear node context on deploy. Closes #870 + - #870 Clear node context on deploy. Closes - Enable finer grained permissions in adminAuth Nodes @@ -1619,9 +1673,9 @@ Nodes - Enable config nodes to reference other config nodes - Add Split/Join nodes - Add Link nodes - - Add support to HTTP In node for PATCH requests. Closes #904 + - #904 Add support to HTTP In node for PATCH requests. Closes - Add cookie handling to HTTP In and HTTP Response nodes - - Add repeat indicator to inject node label. Closes #887 + - #887 Add repeat indicator to inject node label. Closes - Add javascript highlighter to template node - Add optional timeout to exec node - Add TLS node and update MQTT/HTTP nodes to use it @@ -1633,19 +1687,19 @@ Nodes - Update Serial node to support custom baud rates - Add support for array-syntax in typedInput msg properties - Add RED.util to Function node sandbox - - Capture error stack on node.error. Closes #879 + - #879 Capture error stack on node.error. Closes Fixes - Add error handling to all node definition api calls - Handle null return from Function node in array of messages - - Defer loading of token sessions until they are accessed. Fixes #895 + - #895 Defer loading of token sessions until they are accessed. Fixes - set pi gpio pin status correctly if set on start - - Prevent parent window scrolling when view is focused. Fixes #635 + - #635 Prevent parent window scrolling when view is focused. Fixes - Handle missing tab nodes in a loaded flow config - Ensure typedInput dropdown doesn't fall off the page - - Protect against node types with reserved names such as toString. Fixes #880 + - #880 Protect against node types with reserved names such as toString. Fixes - Do not rely on the HTML file to identify where nodes are registered from - Preserve node properties on import - Fix regression in delay node. topic based queue was emptying all the time instead of spreading out messages. @@ -1661,22 +1715,22 @@ Fixes #### 0.13.4: Maintenance Release - Add timed release mode to delay node - - Enable link splicing for when import_dragging nodes. Closes #811 + - #811 Enable link splicing for when import_dragging nodes. Closes - Fix uncaught exception on deploy whilst node sending messages - Deprecate old mqtt client and connection pool modules - - Change node: add bool/num types to change mode Closes #835 - - Validate fields that are `$(env-vars)` Closes #825 + - #835 Change node: add bool/num types to change mode Closes + - #825 Validate fields that are `$(env-vars)` Closes - Handle missing config nodes when validating node properties - Pi node - don't try to send data if closing - Load node message catalog when added dynamically - Split palette labels on spaces and hyphens when laying out - - Warn if editor routes are accessed but runtime not started Closes #816 - - Better handling of zero-length flow files Closes #819 + - #816 Warn if editor routes are accessed but runtime not started Closes + - #819 Better handling of zero-length flow files Closes - Allow runtime calls to RED._ to specify other namespace - Better right alignment of numerics in delay and trigger nodes - Allow node modules to include example flows - Create node_modules in userDir - - Ensure errors in node def functions don't break view rendering Fixes #815 + - #815 Ensure errors in node def functions don't break view rendering Fixes - Updated Inject node info with instructions for flow and global options @@ -1700,13 +1754,13 @@ Fixes - Make jquery spinner element css consistent with other inputs - tcp node add reply (to all) capability - Allow the template node to be treated as plain text - - Validate MQTT In topics Fixes #792 - - httpNodeAuth should not block http options requests Fixes #793 + - #792 Validate MQTT In topics Fixes + - #793 httpNodeAuth should not block http options requests Fixes - Disable perMessageDeflate on WS servers - fixes 'zlib binding closed' error - Clear trigger status icon on re-deploy - Don't default inject payload to blank string - Trigger node, add configurable reset - - Allow function properties in settings Fixes #790 - fixes use of httpNodeMiddleware + - #790 - fixes use of httpNodeMiddleware Allow function properties in settings Fixes - Fix order of config dialog calls to save/creds/validate - Add debounce to Pi GPIO node @@ -1722,7 +1776,7 @@ Fixes - Add 'previous value' option to Switch node - Allow existing nodes to splice into links on drag - - CORS not properly configured on multiple http routes Fixes #783 + - #783 CORS not properly configured on multiple http routes Fixes - Restore shift-drag to snap/unsnap to grid - Moving nodes with keyboard should flag workspace dirty - Notifications flagged as fixed should not be click-closable diff --git a/Gruntfile.js b/Gruntfile.js index cbf70f4dc..7f7e80f32 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -193,7 +193,8 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", - "packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js", + "node_modules/marked/marked.min.js", + "node_modules/dompurify/dist/purify.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js", "node_modules/jsonata/jsonata-es5.min.js", diff --git a/package.json b/package.json index a6c4c5091..ca61f0e49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.3", + "version": "1.0.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -24,7 +24,7 @@ } ], "dependencies": { - "ajv": "6.10.2", + "ajv": "6.12.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -34,47 +34,49 @@ "cookie": "0.4.0", "cookie-parser": "1.4.4", "cors": "2.8.5", - "cron": "1.7.2", + "cron": "1.8.2", "denque": "1.4.1", "express": "4.17.1", "express-session": "1.17.0", "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", - "https-proxy-agent": "2.2.4", + "https-proxy-agent": "5.0.0", "i18next": "15.1.2", - "iconv-lite": "0.5.0", + "iconv-lite": "0.5.1", "is-utf8": "0.2.1", "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.0", + "jsonata": "1.8.1", "media-typer": "1.1.0", - "memorystore": "1.6.1", + "memorystore": "1.6.2", "mime": "2.4.4", "mqtt": "2.18.8", "multer": "1.4.2", - "mustache": "3.0.2", + "mustache": "4.0.0", "node-red-node-rbe": "^0.2.6", "node-red-node-sentiment": "^0.1.6", "node-red-node-tail": "^0.1.0", "nopt": "4.0.1", "oauth2orize": "1.11.0", "on-headers": "1.0.2", - "passport": "0.4.0", + "passport": "0.4.1", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "raw-body": "2.4.1", "request": "2.88.0", "semver": "6.3.0", - "uglify-js": "3.6.9", + "uglify-js": "3.8.0", "when": "3.7.8", "ws": "6.2.1", - "xml2js": "0.4.22" + "xml2js": "0.4.23" }, "optionalDependencies": { "bcrypt": "3.0.6" }, "devDependencies": { + "marked": "0.8.0", + "dompurify": "2.0.8", "grunt": "~1.0.4", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", @@ -102,7 +104,7 @@ "mocha": "^5.2.0", "mosca": "^2.8.3", "node-red-node-test-helper": "^0.2.3", - "node-sass": "^4.13.0", + "node-sass": "^4.13.1", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.1.0", diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index 88e924e53..d4ec10f08 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -101,7 +101,10 @@ function login(req,res) { } } else if (mergedAdminAuth.type === "strategy") { - var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot; + var urlPrefix = (settings.httpAdminRoot||"").replace(/\/$/,""); + if (urlPrefix.length > 0) { + urlPrefix += "/"; + } response = { "type":"strategy", "prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 824f4df5b..9866a81ad 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,21 +16,21 @@ } ], "dependencies": { - "@node-red/util": "1.0.3", - "@node-red/editor-client": "1.0.3", + "@node-red/util": "1.0.4", + "@node-red/editor-client": "1.0.4", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", "cors": "2.8.5", "express-session": "1.17.0", "express": "4.17.1", - "memorystore": "1.6.1", + "memorystore": "1.6.2", "mime": "2.4.4", - "mustache": "3.0.2", + "mustache": "4.0.0", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", - "passport": "0.4.0", + "passport": "0.4.1", "when": "3.7.8", "ws": "6.2.1" }, 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 52e1c71ca..887cf5ebf 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -978,7 +978,8 @@ "passphrase": "Passphrase", "retry": "Retry", "update-failed": "Failed to update auth", - "unhandled": "Unhandled error response" + "unhandled": "Unhandled error response", + "host-key-verify-failed": "

Host key verification failed.

The repository host key could not be verified. Please update your known_hosts file and try again." }, "create-branch-list": { "invalid": "Invalid branch", diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index 3695eb263..95d3fa5ee 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -24,7 +24,7 @@ "buffer": "buffer", "object": "对象", "jsonString": "JSON字符串", - "undefined": "为定义", + "undefined": "未定义", "null": "空" } }, @@ -1008,6 +1008,7 @@ "en-US": "英文", "ja": "日语", "ko": "韩文", - "zh-CN": "简体中文" + "zh-CN": "简体中文", + "zh-TW": "繁体中文" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json index 56ede1b4e..f27ec1f51 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json @@ -262,5 +262,9 @@ "$distinct": { "args": "array", "desc": "返回一个数组,其中重复的值已从`数组`中删除" + }, + "$type": { + "args": "value", + "desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json index 492fce58c..897599bc7 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json @@ -15,6 +15,17 @@ "next": "下一步", "clone": "複製專案", "cont": "Continue" + }, + "type": { + "string": "字符串", + "number": "數值", + "boolean": "布林", + "array": "數組", + "buffer": "buffer", + "object": "對象", + "jsonString": "JSON字符串", + "undefined": "未定義", + "null": "空" } }, "workspace": { @@ -29,8 +40,7 @@ "enabled": "有效", "disabled": "無效", "info": "詳細描述", - "selectNodes": "點擊節點用於選擇", - "tip": "詳細描述支援Markdown羽量級標記語言,並將出現在資訊標籤中。" + "selectNodes": "點擊節點用於選擇" }, "menu": { "label": { @@ -45,14 +55,14 @@ "ltr": "從左到右", "rtl": "從右到左", "auto": "上下文", - "language": "Language", - "browserDefault": "Browser default" + "language": "語言", + "browserDefault": "瀏覽器默認" }, "sidebar": { "show": "顯示側邊欄" }, "palette": { - "show": "Show palette" + "show": "顯示控制板" }, "settings": "設置", "userSettings": "使用者設置", @@ -81,10 +91,7 @@ "projects-new": "新專案", "projects-open": "開啟專案", "projects-settings": "專案設定", - "showNodeLabelDefault": "顯示新添加節點的標籤", - "clipboard": "剪貼簿", - "library": "庫", - "examples": "範例" + "showNodeLabelDefault": "顯示新添加節點的標籤" } }, "actions": { @@ -204,8 +211,7 @@ }, "copyMessagePath": "已複製路徑", "copyMessageValue": "已複製數值", - "copyMessageValue_truncated": "已複製捨棄的數值", - "selectNodes": "選擇上面的文本並複製到剪貼簿" + "copyMessageValue_truncated": "已複製捨棄的數值" }, "deploy": { "deploy": "部署", @@ -237,7 +243,7 @@ "undeployedChanges": "您有未部署的更改。\n\n離開此頁面將丟失這些更改。", "improperlyConfigured": "工作區包含一些未正確配置的節點:", "unknown": "工作區包含一些未知的節點類型:", - "confirm": "你確定要部署嗎?", + "confirm": "確定要部署嗎?", "doNotWarn": "不要再對此發出警告", "conflict": "伺服器正在運行較新的一組流程。", "backgroundUpdate": "伺服器上的流程已更新。", @@ -300,8 +306,7 @@ "errors": { "noNodesSelected": "無法創建子流程: 未選擇節點", "multipleInputsToSelection": "無法創建子流程: 多個輸入到了選擇" - }, - "format": "標記格式" + } }, "editor": { "configEdit": "編輯", @@ -316,17 +321,53 @@ "addNewType": "添加新的__type__節點", "nodeProperties": "節點屬性", "label": "Label", + "color": "顏色", "portLabels": "埠標籤", "labelInputs": "輸入", "labelOutputs": "輸出", "settingIcon": "Icon", + "default": "默認", "noDefaultLabel": "無", "defaultLabel": "使用默認標籤", - "searchIcons": "搜尋 icons", + "searchIcons": "搜尋圖標", "useDefault": "使用默認", "description": "描述", "show": "顯示", "hide": "隱藏", + "locale": "選擇界面語言", + "icon": "圖標", + "inputType": "輸入類型", + "inputs": { + "input": "輸入", + "select": "選擇", + "checkbox": "復選框", + "spinner": "微調器", + "none": "空", + "hidden": "隱藏屬性" + }, + "types": { + "str": "字符串", + "num": "數字", + "bool": "布爾", + "json": "JSON", + "bin": "buffer", + "env": "環境變量" + }, + "menu": { + "input": "輸入", + "select": "選擇", + "checkbox": "復選框", + "spinner": "微調器", + "hidden": "僅標簽" + }, + "select": { + "label": "標簽", + "value": "值" + }, + "spinner": { + "min": "最小值", + "max": "最大值" + }, "errors": { "scopeChange": "更改範圍將使其他流程中的節點無法使用", "invalidProperties": "無效的屬性:" @@ -356,8 +397,9 @@ "cutNode": "剪切所選節點", "pasteNode": "粘貼節點", "undoChange": "撤銷上次執行的更改", - "searchBox": "打開搜索框", - "managePalette": "管理面板" + "searchBox": "打開搜尋框", + "managePalette": "管理面板", + "actionList": "動作列表" }, "library": { "library": "庫", @@ -371,28 +413,27 @@ "savedNodes": "保存的節點", "savedType": "已保存__type__", "saveFailed": "保存失敗: __message__", + "newFolder": "新文件夾", "types": { "local": "本地", "examples": "例子" }, - "exportToLibrary": "將節點匯出到庫", - "filename": "檔案名", - "folder": "資料夾", - "filenamePlaceholder": "文件", - "fullFilenamePlaceholder": "a/b/文件", - "folderPlaceholder": "a/b", - "breadcrumb": "庫" + "exportToLibrary": "將節點匯出到庫" }, "palette": { "noInfo": "無可用資訊", "filter": "過濾節點", - "search": "搜索模組", + "search": "搜尋模組", "addCategory": "添加新的...", "label": { "subflows": "子流程", + "network": "網絡", + "common": "共通", "input": "輸入", "output": "輸出", "function": "功能", + "sequence": "序列", + "parser": "解析", "social": "社交", "storage": "存儲", "analysis": "分析", @@ -459,7 +500,7 @@ "sortRecent": "日期順序", "more": "增加__count__個", "errors": { - "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制台瞭解更多資訊", + "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制臺瞭解更多資訊", "installFailed": "無法安裝: __module__
__message__
查看日誌瞭解更多資訊", "removeFailed": "無法刪除: __module__
__message__
查看日誌瞭解更多資訊", "updateFailed": "無法更新: __module__
__message__
查看日誌瞭解更多資訊", @@ -529,8 +570,10 @@ "none": "無", "subflows": "子流程", "flows": "流程", - "filterUnused": "未使用", "filterAll": "所有", + "showAllConfigNodes": "顯示所有配置節點", + "filterUnused": "未使用", + "showAllUnusedConfigNodes": "顯示所有未使用的配置節點", "filtered": "__count__ 個隱藏" }, "context": { @@ -543,7 +586,9 @@ "flow": "流程", "global": "全局的", "deleteConfirm": "你確定要刪除這個項目嗎?", - "autoRefresh": "自動刷新" + "autoRefresh": "自動刷新", + "refrsh": "刷新", + "delete": "刪除" }, "palette": { "name": "節點管理", @@ -558,6 +603,7 @@ "noSummaryAvailable": "無可用摘要", "editDescription": "編輯專案描述", "editDependencies": "編輯項目依賴", + "noDescriptionAvailable": "沒有可用的描述", "editReadme": "Edit README.md", "showProjectSettings": "顯示項目設置", "projectSettings": { @@ -657,15 +703,15 @@ "moreCommits": "更多提交", "changeLocalBranch": "變更當地分支", "createBranchPlaceholder": "查找或創建分支", - "upstream": "上游的", - "localOverwrite": "您有可通过切换分支覆盖的本地更改。您必须先提交或撤销那些更改。", + "upstream": "上遊的", + "localOverwrite": "您有可通過切換分支覆蓋的本地更改。您必須先提交或撤銷那些更改。", "manageRemoteBranch": "管理遠程分支", "unableToAccess": "無法訪問遠程存儲庫", "retry": "重試", - "setUpstreamBranch": "設置為上游分支", + "setUpstreamBranch": "設置為上遊分支", "createRemoteBranchPlaceholder": "查找或創建遠程分支", - "trackedUpstreamBranch": "創建的分支將被設置為跟踪的上游分支。", - "selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟踪的上游分支。", + "trackedUpstreamBranch": "創建的分支將被設置為跟蹤的上遊分支。", + "selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟蹤的上遊分支。", "pushFailed": "Push失敗,因為遠程具有更多的最新提交。請先進行pull與merge,然後再嘗試push。", "push": "push", "pull": "pull", @@ -683,7 +729,7 @@ "minsAgo": "__count__分鐘前", "minsAgo_plural": "__count__分鐘前", "secondsAgo": "秒前", - "notTracking": "您的本地分支當前未跟踪遠程分支。", + "notTracking": "您的本地分支當前未跟蹤遠程分支。", "statusUnmergedChanged": "您的存儲庫中有未合併的更改。您需要解決衝突並提交結果。", "repositoryUpToDate": "您的存儲庫是最新的。", "commitsAhead": "您的倉庫領先遠程倉庫__count__次提交。您現在可以push這些提交。", @@ -748,10 +794,23 @@ }, "jsonEditor": { "title": "JSON編輯器", - "format": "格式化JSON" + "format": "格式化JSON", + "rawMode": "編輯 JSON", + "uiMode": "Visual編輯器", + "insertAbove": "在上方插入", + "insertBelow": "在下方插入", + "addItem": "添加項目", + "copyPath": "復制路徑到項目", + "expandItems": "展開項目", + "collapseItems": "收合項目", + "duplicate": "重復", + "error": { + "invalidJSON": "無效的JSON: " + } }, "markdownEditor": { "title": "Markdown 編輯器", + "expand": "展開", "format": "F使用markdown格式化", "heading1": "Heading 1", "heading2": "Heading 2", @@ -786,7 +845,7 @@ }, "git-config": { "setup": "設置您的版本控制客戶端", - "desc0": "Node-RED使用開源工具Git進行版本控制。 它跟踪對項目文件的更改,並允許您將其推送到遠程存儲庫。", + "desc0": "Node-RED使用開源工具Git進行版本控制。 它跟蹤對項目文件的更改,並允許您將其推送到遠程存儲庫。", "desc1": "提交一組更改時,Git會使用用戶名和電子郵件地址記錄誰進行了更改。 用戶名可以是您想要的任何名稱-不必是您的真實姓名。", "desc2": "您的Git客戶端已經配置了以下詳細信息。", "desc3": "您可以稍後在設置對話框的“ Git config”標籤下更改這些設置。", @@ -905,7 +964,7 @@ "confirm": "您確定要刪除此項目嗎?" }, "create-project-list": { - "search": "搜索您的項目", + "search": "搜尋您的項目", "current": "當前的" }, "require-clean": { @@ -938,8 +997,19 @@ }, "editor-tab": { "properties": "屬性", + "envProperties": "環境變量", "description": "描述", "appearance": "外觀", + "preview": "UI預覽", + "defaultValue": "默認值", "env": "環境變量" + }, + "languages": { + "de": "德語", + "en-US": "英語", + "ja": "日語", + "ko": "韓語", + "zh-CN": "簡體中文", + "zh-TW": "繁體中文" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json index ae62fe068..6d99ffc6a 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json @@ -214,5 +214,57 @@ "$toMillis": { "args": "timestamp", "desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜)開始到現在的毫秒數。如果該字串的格式不正確,則拋出錯誤。" + }, + "$env": { + "args": "arg", + "desc": "返回環境變量的值。\n\n這是Node-RED定義的函數。" + }, + "$eval": { + "args": "expr [, context]", + "desc": "使用當前上下文來作為評估依據,分析並評估字符串`expr`,其中包含文字JSON或JSONata表達式。" + }, + "$formatInteger": { + "args": "number, picture", + "desc": "將“數字”轉換為字符串,並將其格式化為“圖片”字符串指定的整數表示形式。圖片字符串參數定義了數字的格式,並具有與XPath F&O 3.1 規範中的fn:format-integer相同的語法。" + }, + "$parseInteger": { + "args": "string, picture", + "desc": "使用“圖片”字符串指定的格式將“字符串”參數的內容解析為整數(作為JSON數字)。圖片字符串參數與$formatInteger格式相同。." + }, + "$error": { + "args": "[str]", + "desc": "引發錯誤並顯示一條消息。 可選的`str`將替代$error()函數評估的默認消息。" + }, + "$assert": { + "args": "arg, str", + "desc": "如果`arg`為真,則該函數返回。 如果arg為假,則拋出帶有str的異常作為異常消息。" + }, + "$single": { + "args": "array, function", + "desc": "返回滿足參數function謂語的array參數中的唯一值 (比如:傳遞值時,函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數:`function(value [,index [,array []]])`其中value是數組的每個輸入,index是該值的位置,整個數組作為第三個參數傳遞。" + }, + "$encodeUrl": { + "args": "str", + "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)組件進行編碼。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" + }, + "$encodeUrlComponent": { + "args": "str", + "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)進行編碼。\n\n示例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" + }, + "$decodeUrl": { + "args": "str", + "desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" + }, + "$decodeUrlComponent": { + "args": "str", + "desc": "解碼先前由encodeUrl創建的統一資源定位符(URL)。 \n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" + }, + "$distinct": { + "args": "array", + "desc": "返回一個數組,其中重復的值已從`數組`中刪除" + }, + "$type": { + "args": "value", + "desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`" } } diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 7f8061c6d..0b3e16e7f 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "repository": { "type": "git", 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 a742577d7..f0c11cdd9 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 @@ -431,7 +431,7 @@ var RED = (function() { ''+ ''; - RED.sidebar.info.set(aboutHeader+marked(data)); + RED.sidebar.info.set(aboutHeader+RED.utils.renderMarkdown(data)); RED.sidebar.info.show(); }); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js index 214668485..b707ccbf5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js @@ -38,7 +38,10 @@ this.element.addClass("red-ui-searchBox-input"); this.uiContainer = this.element.wrap("

").parent(); this.uiContainer.addClass("red-ui-searchBox-container"); - + if (this.element.parents("form").length === 0) { + var form = this.element.wrap("
").parent(); + form.addClass("red-ui-searchBox-form"); + } $('').prependTo(this.uiContainer); this.clearButton = $('').appendTo(this.uiContainer); this.clearButton.on("click",function(e) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 5d4a491b0..b5b25d9d5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -588,8 +588,8 @@ RED.editor = (function() { // cases, and also prevent browser auto-fill of password // - the elements cannot be hidden otherwise Chrome will ignore them. // - the elements need to have id's that imply password/username - $('
').prependTo(dialogForm); - $('
').prependTo(dialogForm); + $('').prependTo(dialogForm); + $('').prependTo(dialogForm); dialogForm.on("submit", function(e) { e.preventDefault();}); dialogForm.find('input').attr("autocomplete","off"); return dialogForm; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js index c8a93964d..9a9765c35 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js @@ -102,7 +102,7 @@ var f = $(this).val(); var args = RED._('jsonata:'+f+".args",{defaultValue:''}); var title = "
"+f+"("+args+")
"; - var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''})); + var body = RED.utils.renderMarkdown(RED._('jsonata:'+f+'.desc',{defaultValue:''})); $("#red-ui-editor-type-expression-help").html(title+"

"+body+"

"); }) 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 97957eadf..f89c8f3a7 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 @@ -107,7 +107,7 @@ clearTimeout(changeTimer); changeTimer = setTimeout(function() { var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop(); - $(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue())); + $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop); },200); }) @@ -116,7 +116,7 @@ } if (value) { - $(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue())); + $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); } panels = RED.panels.create({ id:"red-ui-editor-type-markdown-panels", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index 524c1c16e..3e79f6eb1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -524,12 +524,12 @@ RED.keyboard = (function() { var pane = $('
'); $('
'+ - '
'+ + '
'+ '
'+ '
'+ '
').appendTo(pane); - pane.find("input").searchBox({ + pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({ delay: 100, change: function() { var filterValue = $(this).val().trim(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 8491b5b07..97214ace9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -384,6 +384,7 @@ RED.palette.editor = (function() { handleCatalogResponse(null,catalog,index,v); refreshNodeModuleList(); }).fail(function(jqxhr, textStatus, error) { + console.warn("Error loading catalog",catalog,":",error); handleCatalogResponse(jqxhr,catalog,index); }).always(function() { handled++; 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 967a61f51..b70854bfa 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -269,7 +269,7 @@ RED.palette = (function() { RED.view.focus(); var helpText; if (nt.indexOf("subflow:") === 0) { - helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); + helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); } else { helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||(''+RED._("sidebar.info.none")+''); } @@ -370,7 +370,7 @@ RED.palette = (function() { RED.workspaces.show(nt.substring(8)); e.preventDefault(); }); - nodeInfo = marked(def.info||""); + nodeInfo = RED.utils.renderMarkdown(def.info||""); } setLabel(nt,d,label,nodeInfo); @@ -440,7 +440,7 @@ RED.palette = (function() { } else if (portOutput.length !== 0 && sf.out.length === 0) { portOutput.remove(); } - setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||"")); + setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||"")); setIcon(paletteNode,sf); var currentCategory = paletteNode.data('category'); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js index 2d377ba54..f0944c879 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js @@ -158,7 +158,7 @@ RED.projects.settings = (function() { container.empty(); var desc; if (activeProject.description) { - desc = marked(activeProject.description); + desc = RED.utils.renderMarkdown(activeProject.description); } else { desc = '' + RED._("sidebar.project.noDescriptionAvailable") + ''; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js index 4219b189f..170aacea0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js @@ -30,13 +30,13 @@ RED.projects.userSettings = (function() { $('
').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip")); var row = $('
').appendTo(gitconfigContainer); - $('').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); - gitUsernameInput = $('').appendTo(row); + $('').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); + gitUsernameInput = $('').appendTo(row); gitUsernameInput.val(currentGitSettings.user.name||""); row = $('
').appendTo(gitconfigContainer); - $('').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); - gitEmailInput = $('').appendTo(row); + $('').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); + gitEmailInput = $('').appendTo(row); gitEmailInput.val(currentGitSettings.user.email||""); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index 0c297c82a..da54a81c8 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -1939,100 +1939,121 @@ RED.projects = (function() { } }).fail(function(xhr,textStatus,err) { var responses; + if (options.responses && options.responses[xhr.status]) { responses = options.responses[xhr.status]; if (typeof responses === 'function') { resultCallback = responses; resultCallbackArgs = {error:responses.statusText}; return; - } else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') { - var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; + } else if (options.handleAuthFail !== false && (xhr.responseJSON.code === 'git_auth_failed' || xhr.responseJSON.code === 'git_host_key_verification_failed')) { + if (xhr.responseJSON.code === 'git_auth_failed') { + var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; - var message = $('
'+ + var message = $('
'+ '
'+RED._("projects.send-req.auth-req")+':
'+ '
'+url+'
'+ '
'); - var isSSH = false; - if (/^https?:\/\//.test(url)) { - $('
'+ - '
').appendTo(message); - } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { - isSSH = true; - var row = $('
').appendTo(message); - $('').appendTo(row); - var projectRepoSSHKeySelect = $('
'+ + '
').appendTo(message); + } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { + isSSH = true; + var row = $('
').appendTo(message); + $('').appendTo(row); + var projectRepoSSHKeySelect = $('').appendTo(row); - } + row = $('
').appendTo(message); + $('').appendTo(row); + $('').appendTo(row); + } - var notification = RED.notify(message,{ - type:"error", - fixed: true, - modal: true, - buttons: [ - { - //id: "node-dialog-delete", - //class: 'leftButton', - text: RED._("common.label.cancel"), - click: function() { - notification.close(); - } - },{ - text: ' ' +RED._("projects.send-req.retry") +'', - click: function() { - body = body || {}; - var authBody = {}; - if (isSSH) { - authBody.keyFile = $('#projects-user-auth-key').val(); - authBody.passphrase = $('#projects-user-auth-passphrase').val(); - } else { - authBody.username = $('#projects-user-auth-username').val(); - authBody.password = $('#projects-user-auth-password').val(); + var notification = RED.notify(message,{ + type:"error", + fixed: true, + modal: true, + buttons: [ + { + //id: "node-dialog-delete", + //class: 'leftButton', + text: RED._("common.label.cancel"), + click: function() { + notification.close(); } - var done = function(err) { - if (err) { - console.log(RED._("projects.send-req.update-failed")); - console.log(err); + },{ + text: ' ' +RED._("projects.send-req.retry") +'', + click: function() { + body = body || {}; + var authBody = {}; + if (isSSH) { + authBody.keyFile = $('#projects-user-auth-key').val(); + authBody.passphrase = $('#projects-user-auth-passphrase').val(); } else { - sendRequest(options,body); - notification.close(); + authBody.username = $('#projects-user-auth-username').val(); + authBody.password = $('#projects-user-auth-password').val(); } + var done = function(err) { + if (err) { + console.log(RED._("projects.send-req.update-failed")); + console.log(err); + } else { + sendRequest(options,body); + notification.close(); + } - } - sendRequest({ - url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'), - type: "PUT", - responses: { - 0: function(error) { - done(error,null); - }, - 200: function(data) { - done(null,data); - }, - 400: { - 'unexpected_error': function(error) { - done(error,null); - } - }, } - },{auth:authBody}); + sendRequest({ + url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'), + type: "PUT", + responses: { + 0: function(error) { + done(error,null); + }, + 200: function(data) { + done(null,data); + }, + 400: { + 'unexpected_error': function(error) { + done(error,null); + } + }, + } + },{auth:authBody}); + } } - } - ] - }); - return; + ] + }); + return; + } else if (xhr.responseJSON.code === 'git_host_key_verification_failed') { + var message = $('
'+ + '
'+RED._("projects.send-req.host-key-verify-failed")+'
'+ + '
'); + var notification = RED.notify(message,{ + type:"error", + fixed: true, + modal: true, + buttons: [ + { + text: RED._("common.label.close"), + click: function() { + notification.close(); + } + } + ] + }); + return; + } } else if (responses[xhr.responseJSON.code]) { resultCallback = responses[xhr.responseJSON.code]; resultCallbackArgs = xhr.responseJSON; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js index 3bc41b982..9994d5000 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js @@ -231,7 +231,8 @@ RED.sidebar.context = (function() { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { typeHint: data.format, sourceId: id+"."+k, - tools: tools + tools: tools, + path: "" }).appendTo(propRow.children()[1]); } }) @@ -275,7 +276,8 @@ RED.sidebar.context = (function() { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { typeHint: data.format, sourceId: id+"."+k, - tools: tools + tools: tools, + path: "" }).appendTo(propRow.children()[1]); } }); @@ -295,7 +297,8 @@ RED.sidebar.context = (function() { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { typeHint: v.format, sourceId: id+"."+k, - tools: tools + tools: tools, + path: "" }).appendTo(propRow.children()[1]); if (contextStores.length > 1) { $("",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0])) 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 b8e3b150b..bfa4e4b23 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 @@ -15,17 +15,6 @@ **/ RED.sidebar.info = (function() { - marked.setOptions({ - renderer: new marked.Renderer(), - gfm: true, - tables: true, - breaks: false, - pedantic: false, - sanitize: true, - smartLists: true, - smartypants: false - }); - var content; var sections; var propertiesSection; @@ -314,7 +303,7 @@ RED.sidebar.info = (function() { if (subflowNode && node.type !== "subflow") { // Selected a subflow instance node. // - The subflow template info goes into help - helpText = (marked(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); + helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); } else { helpText = $("script[data-help-name='"+node.type+"']").html()||(''+RED._("sidebar.info.none")+''); } @@ -326,10 +315,10 @@ RED.sidebar.info = (function() { if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - infoText = infoText + marked(textInfo); + infoText = infoText + RED.utils.renderMarkdown(textInfo); } if (node.info) { - infoText = infoText + marked(node.info || "") + infoText = infoText + RED.utils.renderMarkdown(node.info || "") } setInfoText(infoText, infoSection.content); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index d35d1f1ba..a9c1c9500 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -16,6 +16,28 @@ RED.utils = (function() { + window._marked = window.marked; + window.marked = function(txt) { + console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead"); + return renderMarkdown(txt); + } + + _marked.setOptions({ + renderer: new _marked.Renderer(), + gfm: true, + tables: true, + breaks: false, + pedantic: false, + smartLists: true, + smartypants: false + }); + + function renderMarkdown(txt) { + var rendered = _marked(txt); + var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true}) + return cleaned; + } + function formatString(str) { return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→"); } @@ -1053,6 +1075,7 @@ RED.utils = (function() { decodeObject: decodeObject, parseContextKey: parseContextKey, createIconElement: createIconElement, - sanitize: sanitize + sanitize: sanitize, + renderMarkdown: renderMarkdown } })(); 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 44f75b481..5a9a19031 100755 --- 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 @@ -3617,8 +3617,10 @@ RED.view = (function() { if (new_ms.length === 1) { node = new_ms[0]; spliceActive = node.n.hasOwnProperty("_def") && - node.n._def.inputs > 0 && - node.n._def.outputs > 0; + ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) + + } } RED.keyboard.add("*","escape",function(){ diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss index 2f0740d61..d0cd631fc 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss @@ -32,6 +32,9 @@ right: 5px; top: 9px; } + form.red-ui-searchBox-form { + margin: 0; + } input.red-ui-searchBox-input { border-radius: 0; border: none; diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 382537be3..49a02c206 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -11,6 +11,7 @@ var length = str.length; var start = 0; var inString = false; + var inRegex = false; var inBox = false; var quoteChar; var list = []; @@ -24,8 +25,13 @@ } for (var i=0;i 70) { + post = result.substring(offset+f.pos+1); + if (!/^\n/.test(post)) { + indented = indentLine(post,indent); + hasNewline = /\n$/.test(pre); + result = pre+(hasNewline?"":"\n")+indented; + offset += indented.length-post.length+(hasNewline?0:1); + } + } + } else if (f.type === "open-block") { - if (f.width > 30) { + if (f.width > 40) { longStack.push(true); indent += 4; pre = result.substring(0,offset+f.pos+1); post = result.substring(offset+f.pos+1); + hasNewline = /\n$/.test(pre); indented = indentLine(post,indent); - result = pre+"\n"+indented; - offset += indented.length-post.length+1; + result = pre+(hasNewline?"":"\n")+indented; + offset += indented.length-post.length+(hasNewline?0:1); } else { longStack.push(false); } } else if (f.type === "close-block") { - if (f.width > 30) { + if (f.width > 40) { indent -= 4; pre = result.substring(0,offset+f.pos); post = result.substring(offset+f.pos); indented = indentLine(post,indent); - result = pre+"\n"+indented; - offset += indented.length-post.length+1; + hasNewline = /\n *$/.test(pre); + if (hasNewline) { + result = pre + post; + } else { + result = pre+"\n"+indented; + offset += indented.length-post.length+1; + } } longStack.pop(); } diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js index 2a90e4ce7..48358517e 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js @@ -28,6 +28,11 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m }, "identifier"); this.$rules = { "start" : [ + { + token: "string.regexp", + regex: "\\/", + next: "regex" + }, { token : "string", regex : "'(?=.)", @@ -46,34 +51,35 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "constant.numeric", // float regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/ }, - { token: "keyword", - regex: /λ/ - }, - { - token: "keyword", - regex: jsonataFunctions - }, - { - token : keywordMapper, - regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*" - }, - { - token : "punctuation.operator", - regex : /[.](?![.])/ - }, - { - token : "keyword.operator", - regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/, - next : "start" - }, - { - token : "punctuation.operator", - regex : /[?:,;.]/, - next : "start" - }, - { - token : "paren.lparen", - regex : /[\[({]/, + { + token: "keyword", + regex: /λ/ + }, + { + token: "keyword", + regex: jsonataFunctions + }, + { + token : keywordMapper, + regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*" + }, + { + token : "punctuation.operator", + regex : /[.](?![.])/ + }, + { + token : "keyword.operator", + regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/, + next : "start" + }, + { + token : "punctuation.operator", + regex : /[?:,;.]/, + next : "start" + }, + { + token : "paren.lparen", + regex : /[\[({]/, next : "start" }, { @@ -86,7 +92,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "string", regex : '"|$', next : "start" - }, { + }, + { defaultToken: "string" } ], @@ -95,9 +102,24 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "string", regex : "'|$", next : "start" - }, { + }, + { defaultToken: "string" } + ], + "regex" : [ + { + token: "string.regexp", + regex: "\\\\/" + }, + { + token: "string.regexp", + regex: "/[sxngimy]*", + next: "start" + }, + { + defaultToken: "string.regexp" + } ] }; }; diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js deleted file mode 100644 index 555c1dc1d..000000000 --- a/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) - * https://github.com/chjj/marked - */ -(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
"+(escaped?code:escape(code,true))+"\n
"}return'
'+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"
\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return this.options.xhtml?"
    ":"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='
    ";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js index ddab5bbe2..64068ea86 100644 --- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js +++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js @@ -457,7 +457,7 @@ RED.debug = (function() { var metaRow = $('
    ').appendTo(msg); $(''+ getTimestamp()+'').appendTo(metaRow); if (sourceNode) { - $('
    ',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+sanitize(o.name||sourceNode.name||sourceNode.id)) + $('',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id)) .appendTo(metaRow) .on("click", function(evt) { evt.preventDefault(); diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index b029fa668..d20391b59 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -18,7 +18,7 @@
    - +
    @@ -40,12 +40,12 @@
    - +
    - +
    @@ -109,9 +109,11 @@ $(".node-type-duration").hide(); } else if ($(this).val() == "loop") { + if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); } $(".node-type-wait").hide(); $(".node-type-duration").show(); } else { + if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); } $(".node-type-wait").show(); $(".node-type-duration").show(); } @@ -175,9 +177,7 @@ } if ($("#node-then-type").val() == "loop") { $("#node-input-duration").val($("#node-input-duration").val() * -1); - } - - + } } }); diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index d84bf57a8..e14cd27a3 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -76,6 +76,7 @@ module.exports = function(RED) { var node = this; node.topics = {}; + var npay = {}; var pendingMessages = []; var activeMessagePromise = null; var processMessageQueue = function(msg) { @@ -124,6 +125,7 @@ module.exports = function(RED) { node.status({}); } else { + if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); } if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { promise = Promise.resolve(); if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } @@ -188,7 +190,14 @@ module.exports = function(RED) { }); } promise.then(() => { - msg2.payload = node.topics[topic].m2; + if (node.op2type === "payl") { + node.send(npay[topic]); + delete npay[topic]; + } + else { + msg2.payload = node.topics[topic].m2; + node.send(msg2); + } delete node.topics[topic]; if (node.op2type === "payl") { node.send(npay); } else { node.send(msg2); } diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js index a5132230e..f5e7edd87 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js @@ -145,7 +145,7 @@ module.exports = function(RED) { if (error.signal) { msg3.payload.signal = error.signal; } if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); } - node.log('error:' + error); + if (RED.settings.verbose) { node.log('error:' + error); } } else if (node.oldrc === "false") { msg3 = RED.util.cloneMessage(msg); diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index 6f682b279..7ab624222 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -153,7 +153,12 @@ module.exports = function(RED) { this.brokerurl="mqtt://"; } if (this.broker !== "") { - this.brokerurl = this.brokerurl+this.broker+":"; + //Check for an IPv6 address + if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) { + this.brokerurl = this.brokerurl+"["+this.broker+"]:"; + } else { + this.brokerurl = this.brokerurl+this.broker+":"; + } // port now defaults to 1883 if unset. if (!this.port){ this.brokerurl = this.brokerurl+"1883"; @@ -464,6 +469,7 @@ module.exports = function(RED) { this.broker = n.broker; this.brokerConn = RED.nodes.getNode(this.broker); var node = this; + var chk = /[\+#]/; if (this.brokerConn) { this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); @@ -482,6 +488,7 @@ module.exports = function(RED) { } if ( msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist + if (chk.test(msg.topic)) { node.warn(RED._("mqtt.errors.invalid-topic")); } this.brokerConn.publish(msg, done); // send the message } else { node.warn(RED._("mqtt.errors.invalid-topic")); diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html index e3d75e11f..523dc2629 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html @@ -163,7 +163,7 @@ if (root === "") { $("#node-config-ws-tip").hide(); } else { - $("#node-config-ws-path").html(root); + $("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root })); $("#node-config-ws-tip").show(); } } @@ -235,7 +235,7 @@
    -

    .

    +

    diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 5d93c8faf..ced637555 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -292,7 +292,6 @@ module.exports = function(RED) { reduceMessageGroup(node,msgs,exp,fixup,count,result,done); } }); - } function reduceAndSendGroup(node, group, done) { var is_right = node.reduce_right; @@ -331,7 +330,7 @@ module.exports = function(RED) { var pending_count = node.pending_count; var gid = msg.parts.id; var count; - if(!pending.hasOwnProperty(gid)) { + if (!pending.hasOwnProperty(gid)) { if(parts.hasOwnProperty('count')) { count = msg.parts.count; } @@ -361,7 +360,6 @@ module.exports = function(RED) { } return done(); } - if (msgs.length === group.count) { delete pending[gid]; pending_count -= msgs.length; @@ -408,7 +406,7 @@ module.exports = function(RED) { if (this.joinerType === "str") { this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0"); } else if (this.joinerType === "bin") { - var joinArray = JSON.parse(n.joiner) + var joinArray = JSON.parse(n.joiner || "[]"); if (Array.isArray(joinArray)) { this.joiner = Buffer.from(joinArray); } else { @@ -429,7 +427,7 @@ module.exports = function(RED) { var completeSend = function(partId) { var group = inflight[partId]; - clearTimeout(group.timeout); + if (group.timeout) { clearTimeout(group.timeout); } if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; } if (group.type === 'array' && group.arrayLen > 1) { var newArray = []; @@ -448,6 +446,9 @@ module.exports = function(RED) { buffers.push(joinBuffer); bufferLen += joinBuffer.length; } + if (!Buffer.isBuffer(group.payload[i])) { + group.payload[i] = Buffer.from(group.payload[i]); + } buffers.push(group.payload[i]); bufferLen += group.payload[i].length; } @@ -629,7 +630,13 @@ module.exports = function(RED) { var group = inflight[partId]; if (payloadType === 'buffer') { if (property !== undefined) { - inflight[partId].bufferLen += property.length; + if (Buffer.isBuffer(property) || (typeof property === "string") || Array.isArray(property)) { + inflight[partId].bufferLen += property.length; + } + else { + node.error(RED._("join.errors.invalid-type",{error:(typeof property)}),msg); + return; + } } } if (payloadType === 'object') { diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index c36c309f3..58fce3a92 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -397,7 +397,7 @@ "message" : "gesamte Nachricht", "tip" : { "path1" : "Standardmäßig enthält Nutzdaten die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Listener kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt.", - "path2" : "Dieser Pfad ist relativ zu ", + "path2" : "Dieser Pfad ist relativ zu __path__.", "url1" : "URL sollte ws: / & #47; oder wss: / & #47; Schema verwenden und auf einen vorhandenen Websocket-Listener verweisen.", "url2" : "Standardmäßig enthält Nutzdaten die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt." }, diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html index d69229820..0192c4e13 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html @@ -23,13 +23,13 @@ @@ -37,30 +37,6 @@

    Dieser Konfigurations-Node erstellt einen WebSocket Server-Endpunkt unter Verwendung des angegebenen Pfades.

    - - - 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 06688c326..b5f4474d3 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -455,7 +455,7 @@ "message": "entire message", "tip": { "path1": "By default, payload will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.", - "path2": "This path will be relative to ", + "path2": "This path will be relative to __path__.", "url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.", "url2": "By default, payload will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string." }, diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html index 31bdc4907..173f003f7 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html @@ -41,5 +41,5 @@ wait a fixed timeout from first reply and then return, sit and wait for data, or send then close the connection immediately, without waiting for a reply.

    The response will be output in msg.payload as a buffer, so you may want to .toString() it.

    -

    If you leave tcp host or port blank they must be set by using the msg.host and msg.port properties.

    +

    If you leave tcp host or port blank they must be set by using the msg.host and msg.port properties in every message sent to the node.

    diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html index 118df70af..c9c3e3070 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html @@ -91,7 +91,8 @@
    complete
    -
    If set, the node will send its output message in its current state.
    +
    If set, the node will append the payload, and then send the output message in its current state. + If you don't wish to append the payload, delete it from the msg.

    Details

    diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html index 5933f9c2d..fe2bbc324 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html @@ -49,12 +49,6 @@
    The contents of the file as either a string or binary buffer.
    filename string
    If not configured in the node, this optional property sets the name of the file to be read.
    -
    error object
    -
    deprecated: If enabled in the node, when the node hits an error - reading the file, it will send a message with no payload - and this error property set to the error details. This - mode of behaviour is deprecated and not enabled by default for new - instances of the node. See below for more information.

    Details

    The filename should be an absolute path, otherwise it will be relative to @@ -65,11 +59,5 @@

    When split into multiple messages, each message will have a parts property set, forming a complete message sequence.

    Encoding of input data can be specified from list of encodings if output format is string.

    -

    Legacy error handling

    -

    Before Node-RED 0.17, if this node hit an error whilst reading the file, it would - send a message with no msg.payload and msg.error set to the - details of the error. This is a deprecated mode of behaviour for the node that new - instances will not do. If required, this mode can be re-enabled within the node - configuration.

    Errors should be caught and handled using a Catch node.

    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 adc033390..e15947fa6 100755 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -455,7 +455,7 @@ "message": "メッセージ全体を送信/受信", "tip": { "path1": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。", - "path2": "This path will be relative to ", + "path2": "このパスは __path__ の相対パスになります。", "url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。", "url2": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。" }, @@ -892,7 +892,8 @@ "fixup": "最終調整式" }, "errors": { - "invalid-expr": "JSONata式が不正: __error__" + "invalid-expr": "JSONata式が不正: __error__", + "invalid-type": "__error__ をバッファに連結できません" } }, "sort": { diff --git a/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html index 7702704df..466960a39 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html @@ -79,7 +79,7 @@
    complete
    -
    設定されている場合、保持しているメッセージを結合して送信します。
    +
    設定されている場合、本ノードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください。

    詳細

    diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html index bc971049e..21d4af9be 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html @@ -44,8 +44,6 @@
    ファイルの内容を文字列もしくはバッファで表現します
    filename 文字列
    読み出し対象のファイル名をノードに設定していない場合、このプロパティでファイルを指定します
    -
    error オブジェクト
    -
    非推奨: 設定で有効にした場合、ファイルの読み込み時にエラーが発生するとpayloadを持たずerrorプロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。詳細については、以下を参照してください。

    詳細

    ファイルネームは絶対パスでの指定を推奨します。絶対パスを指定しない場合は、Node-REDプロセスのワーキングディレクトリからの相対パスとして扱います。

    @@ -53,7 +51,5 @@

    テキストファイルの場合、行毎に分割して各々メッセージを送信することができます。また、バイナリファイルの場合、小さな塊のバッファに分割して送信できます。バッファの分割単位はオペレーティングシステム依存ですが、一般に64k(Linux/Mac)もしくは41k(Windows)です。

    複数のメッセージに分割する場合、各メッセージにはpartsプロパティが設定され、メッセージ列を構成します。

    出力形式が文字列の場合、入力データのエンコーディングをエンコーディングリストから選択できます。

    -

    旧式のエラー処理

    -

    Node-RED 0.17より前の版では、ファイルの読み込み時にエラーが発生するとpayloadを持たずerrorプロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。ノードの設定により、必要に応じてこのモードを有効にできます。

    エラーはcatchノードで補足して処理することを推奨します。

    diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json index d87b2c0f5..8e3a4f325 100755 --- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json @@ -446,7 +446,7 @@ "message": "메세지 전체를 송신/수신", "tip": { "path1": "표준으로는 payload 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.", - "path2": "This path will be relative to ", + "path2": "This path will be relative to __path__.", "url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.", "url2": "표준으로는 payload 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다." }, diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 46560d181..934cc734d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -455,7 +455,7 @@ "message": "完整信息", "tip": { "path1": "默认情况下,payload将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.", - "path2": "这条路径将相对于 ", + "path2": "这条路径将相对于 __path__.", "url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.", "url2": "默认情况下,payload 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象." }, @@ -698,7 +698,7 @@ "output": "输出", "includerow": "包含列名行", "newline": "换行符", - "usestrings": "parse numerical values" + "usestrings": "解析数值" }, "placeholder": { "columns": "用逗号分割列名" @@ -898,7 +898,7 @@ } }, "sort" : { - "sort": "sort", + "sort": "排序", "target" : "排序属性", "seq" : "信息队列", "key" : "键值", @@ -907,9 +907,9 @@ "ascending" : "升序", "descending" : "降序", "as-number" : "作为数值", - "invalid-exp" : "sort节点中存在无效的JSONata表达式", - "too-many" : "sort节点中有太多待定信息", - "clear" : "清空sort节点中的待定信息" + "invalid-exp" : "排序节点中存在无效的JSONata表达式", + "too-many" : "排序节点中有太多待定信息", + "clear" : "清空排序节点中的待定信息" }, "batch" : { "batch": "batch", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html index 4ec78cdb4..eb38a5235 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html @@ -14,7 +14,7 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html new file mode 100644 index 000000000..046eddd7e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html new file mode 100644 index 000000000..31f78e907 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html new file mode 100644 index 000000000..862745310 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html new file mode 100644 index 000000000..4e3db015d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html @@ -0,0 +1,36 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html new file mode 100644 index 000000000..d961c8e52 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html new file mode 100644 index 000000000..e7723c499 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html new file mode 100644 index 000000000..d044f28db --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html @@ -0,0 +1,21 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html new file mode 100644 index 000000000..c3588def1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html new file mode 100644 index 000000000..9f8ddb43f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html @@ -0,0 +1,51 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html new file mode 100644 index 000000000..5a65eff93 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html @@ -0,0 +1,37 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html new file mode 100644 index 000000000..91a320945 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html new file mode 100644 index 000000000..62eb63d0b --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html @@ -0,0 +1,40 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html new file mode 100644 index 000000000..874ae3801 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html @@ -0,0 +1,46 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html new file mode 100644 index 000000000..28c291de8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html @@ -0,0 +1,32 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html new file mode 100644 index 000000000..6bbe72f4d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html new file mode 100644 index 000000000..27be34e00 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html @@ -0,0 +1,74 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 153a88300..5368bb993 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -6,7 +6,9 @@ "name": "名稱", "username": "使用者名稱", "password": "密碼", - "property": "屬性" + "property": "屬性", + "selectNodes": "選擇節點...", + "expand": "擴展" }, "status": { "connected": "已連接", @@ -35,7 +37,22 @@ "stopped": "停止", "failed": "注入失敗: __error__", "label": { - "repeat": "重複" + "repeat": "重複", + "flow": "流上下午", + "global": "全局上下文", + "str": "字符串", + "num": "數值", + "bool": "布爾值", + "json": "JSON對象", + "bin": "buffer", + "date": "時間戳", + "env": "環境變量", + "object": "對象", + "string": "字符串", + "boolean": "布爾值", + "number": "數值", + "Array": "數組", + "invalid": "無效的JSON對象" }, "timestamp": "時間戳記", "none": "無", @@ -72,13 +89,11 @@ "catch": { "catch": "監測所有節點", "catchNodes": "監測__number__個節點", + "catchUncaught": "捕獲:未捕獲", "label": { "source": "監測範圍", - "node": "節點", - "type": "類型", "selectAll": "全選", - "sortByLabel": "按名稱排序", - "sortByType": "按類型排序" + "uncaught": "忽略其他捕獲節點處理的錯誤" }, "scope": { "all": "所有節點", @@ -90,10 +105,6 @@ "statusNodes": "報告__number__個節點狀態", "label": { "source": "報告狀態範圍", - "node": "節點", - "type": "類型", - "selectAll": "全選", - "sortByLabel": "按名稱排序", "sortByType": "按類型排序" }, "scope": { @@ -101,8 +112,13 @@ "selected": "指定節點" } }, + "complete": { + "completeNodes": "完成: __number__個節點" + }, "debug": { "output": "輸出", + "none": "None", + "invalid-exp": "無效的JSONata表達式: __error__", "msgprop": "資訊屬性", "msgobj": "完整資訊", "to": "目標", @@ -124,7 +140,11 @@ "filterCurrent": "當前流程", "debugNodes": "除錯節點", "clearLog": "清空日誌", - "openWindow": "在新視窗打開" + "filterLog": "過濾日誌", + "openWindow": "在新視窗打開", + "copyPath": "復制路徑", + "copyPayload": "復制值", + "pinPath": "固定展開" }, "messageMenu": { "collapseAll": "折疊所有路徑", @@ -146,26 +166,33 @@ "key": "私密金鑰", "passphrase": "密碼", "ca": "CA證書", - "verify-server-cert":"驗證伺服器憑證" + "verify-server-cert": "驗證伺服器憑證", + "servername": "服務器名" }, "placeholder": { - "cert":"憑證路徑 (PEM 格式)", - "key":"私密金鑰路徑 (PEM 格式)", - "ca":"CA憑證路徑 (PEM 格式)", - "passphrase":"私密金鑰密碼 (可選)" + "cert": "憑證路徑 (PEM 格式)", + "key": "私密金鑰路徑 (PEM 格式)", + "ca": "CA憑證路徑 (PEM 格式)", + "passphrase": "私密金鑰密碼 (可選)", + "servername": "用於SNI" }, "error": { "missing-file": "未提供證書/金鑰檔案" } }, "exec": { + "exec": "exec", + "spawn": "spawn", "label": { "command": "命令", "append": "追加", "timeout": "超時", "timeoutplace": "可選填", "return": "輸出", - "seconds": "秒" + "seconds": "秒", + "stdout": "標準輸出", + "stderr": "標準錯誤輸出", + "retcode": "返回碼" }, "placeholder": { "extraparams": "額外的輸入參數" @@ -177,16 +204,18 @@ "oldrc": "使用舊式輸出 (相容模式)" }, "function": { + "function": "函數", "label": { "function": "函數", "outputs": "輸出" }, "error": { - "inputListener":"無法在函數中監聽對'注入'事件", - "non-message-returned":"函數節點嘗試返回類型為 __type__ 的資訊" + "inputListener": "無法在函數中監聽對'注入'事件", + "non-message-returned": "函數節點嘗試返回類型為 __type__ 的資訊" } }, "template": { + "template": "模板", "label": { "template": "模版", "property": "屬性", @@ -233,21 +262,21 @@ "limit": "限制", "limitTopic": "限制主題", "random": "隨機", - "units" : { + "units": { "second": { - "plural" : "秒", + "plural": "秒", "singular": "秒" }, "minute": { - "plural" : "分鐘", + "plural": "分鐘", "singular": "分鐘" }, "hour": { - "plural" : "小時", + "plural": "小時", "singular": "小時" }, "day": { - "plural" : "天", + "plural": "天", "singular": "天" } } @@ -272,6 +301,9 @@ "wait-reset": "等待被重置", "wait-for": "等待", "wait-loop": "週期性重發", + "for": "處理", + "bytopics": "每個msg.topic", + "alltopics": "所有消息", "duration": { "ms": "毫秒", "s": "秒", @@ -284,12 +316,13 @@ "trigger-block": "觸發並阻止", "trigger-loop": "週期性重發", "reset": "重置觸發節點條件 如果:", - "resetMessage":"msg.reset已設置", - "resetPayload":"msg.payload等於", + "resetMessage": "msg.reset已設置", + "resetPayload": "msg.payload等於", "resetprompt": "可選填" } }, "comment": { + "comment": "注釋" }, "unknown": { "label": { @@ -303,26 +336,32 @@ "example": "e.g. localhost", "output": "輸出", "qos": "QoS", + "retain": "保持", "clientid": "使用者端ID", "port": "埠", "keepalive": "Keepalive計時(秒)", "cleansession": "使用新的會話", "use-tls": "使用安全連接 (SSL/TLS)", - "tls-config":"TLS 設置", - "verify-server-cert":"驗證伺服器憑證", + "tls-config": "TLS 設置", + "verify-server-cert": "驗證伺服器憑證", "compatmode": "使用舊式MQTT 3.1支援" }, + "sections-label": { + "birth-message": "連接時發送的消息(出生消息)", + "will-message": "意外斷開連接時的發送消息(Will消息)", + "close-message": "斷開連接前發送的消息(關閉消息)" + }, "tabs-label": { "connection": "連接", "security": "安全", - "will": "Will信息", - "birth": "Birth信息" + "messages": "消息" }, "placeholder": { "clientid": "留白則自動隨機生成", - "clientid-nonclean":"如非新會話,必須設置使用者端ID", + "clientid-nonclean": "如非新會話,必須設置使用者端ID", "will-topic": "留白將禁止Will資訊", - "birth-topic": "留白將禁止Birth資訊" + "birth-topic": "留白將禁止Birth資訊", + "close-topic": "留白以禁用關閉消息" }, "state": { "connected": "已連接到服務端: __broker__", @@ -333,7 +372,9 @@ "output": { "buffer": "Buffer", "string": "字串", - "base64": "Base64編碼字串" + "base64": "Base64編碼字串", + "auto": "自動檢測 (字符串或buffer)", + "json": "解析的JSON對象" }, "true": "是", "false": "否", @@ -342,7 +383,9 @@ "not-defined": "主題未設置", "missing-config": "未設置服務端", "invalid-topic": "主題無效", - "nonclean-missingclientid": "使用者端ID未設定,使用新會話" + "nonclean-missingclientid": "使用者端ID未設定,使用新會話", + "invalid-json-string": "無效的JSON字符串", + "invalid-json-parse": "無法解析JSON字符串" } }, "httpin": { @@ -354,12 +397,26 @@ "upload": "接受檔案上傳?", "status": "狀態碼", "headers": "Header", - "other": "其他" + "other": "其他", + "paytoqs": "將msg.payload附加為查詢字符串參數", + "utf8String": "UTF8格式的字符串", + "binaryBuffer": "二進制buffer", + "jsonObject": "解析的JSON對象", + "authType": "類型", + "bearerToken": "Token" }, "setby": "- 用 msg.method 設定 -", "basicauth": "基本認證", "use-tls": "使用安全連接 (SSL/TLS) ", - "tls-config":"TLS 設置", + "tls-config": "TLS 設置", + "basic": "基本認證", + "digest": "摘要認證", + "bearer": "bearer認證", + "use-proxy": "使用代理服務器", + "persist": "對連接啟用keep-alive", + "proxy-config": "代理服務器設置", + "use-proxyauth": "使用代理身份驗證", + "noproxy-hosts": "代理例外", "utf8": "UTF-8 字串", "binary": "二進位資料", "json": "JSON對象", @@ -375,8 +432,11 @@ "no-response": "無響應物件", "json-error": "JSON 解析錯誤", "no-url": "未設定 URL", - "deprecated-call":"__method__方法已棄用", - "invalid-transport":"非HTTP傳輸請求" + "deprecated-call": "__method__方法已棄用", + "invalid-transport": "非HTTP傳輸請求", + "timeout-isnan": "超時值不是有效數字,忽略", + "timeout-isnegative": "超時值為負,忽略", + "invalid-payload": "無效的有效載荷" }, "status": { "requesting": "請求中" @@ -395,17 +455,23 @@ "message": "完整資訊", "tip": { "path1": "預設情況下,payload將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.", - "path2": "這條路徑將相對於 ", + "path2": "這條路徑將相對於 __path__.", "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.", "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件." }, + "status": { + "connected": "連接數 __count__", + "connected_plural": "連接數 __count__" + }, "errors": { "connect-error": "ws連接發生了錯誤: ", "send-error": "發送時發生了錯誤: ", - "missing-conf": "未設置伺服器" + "missing-conf": "未設置伺服器", + "duplicate-path": "同一路徑上不能有兩個WebSocket偵聽器: __path__" } }, "watch": { + "watch": "watch", "label": { "files": "文件", "recursive": "遞迴所有子資料夾" @@ -458,14 +524,12 @@ "connection-closed": "連接已關閉 __host__:__port__", "connections": "__count__ 個連接", "connections_plural": "__count__ 個連接" - }, "errors": { "connection-lost": "連接中斷 __host__:__port__", "timeout": "超時關閉通訊端連接,埠 __port__", "cannot-listen": "無法監聽埠 __port__, 錯誤: __error__", "error": "錯誤: __error__", - "socket-error": "通訊端連接錯誤來自 __host__:__port__", "no-host": "主機位址或埠未設定", "connect-timeout": "連接逾時", @@ -480,14 +544,15 @@ "output": "輸出", "group": "組", "interface": "本地IP", - "interfaceprompt": "(可選)本地 IP 綁定到", "send": "發送一個", "toport": "到埠", "address": "地址", - "decode-base64": "是否解碼Base64編碼的資訊?" + "decode-base64": "是否解碼Base64編碼的資訊?", + "interfaceprompt": "(可選)本地 IP 綁定到" }, "placeholder": { "interface": "(可選)eth0的IP地址", + "interfaceprompt": "(可選) 要綁定的本地接口或地址", "address": "目標IP位址" }, "udpmsgs": "udp信息", @@ -529,36 +594,43 @@ "ip-notset": "udp: IP地址未設定", "port-notset": "udp: 埠未設定", "port-invalid": "udp: 無效埠號碼", - "alreadyused": "udp: 埠已被佔用" + "alreadyused": "udp: 埠已被佔用", + "ifnotfound": "udp: 接口 __iface__ 未發現" } }, "switch": { + "switch": "switch", "label": { "property": "屬性", "rule": "規則", - "repair" : "重建資訊佇列" + "repair": "重建資訊佇列" }, + "previous": "先前值", "and": "與", "checkall": "全選所有規則", "stopfirst": "接受第一條匹配資訊後停止", "ignorecase": "忽略大小寫", "rules": { - "btwn":"在之間", - "cont":"包含", - "regex":"匹配規則運算式", - "true":"為真", - "false":"為假", - "null":"為空", - "nnull":"非空", - "head":"head", - "tail":"tail", - "index":"index between", - "exp":"JSONata運算式", - "else":"除此以外" + "btwn": "在之間", + "cont": "包含", + "regex": "匹配規則運算式", + "true": "為真", + "false": "為假", + "null": "為空", + "nnull": "非空", + "istype": "類型是", + "empty": "為空", + "nempty": "非空", + "head": "head", + "tail": "tail", + "index": "index between", + "exp": "JSONata運算式", + "else": "除此以外", + "hask": "擁有鍵" }, "errors": { "invalid-expr": "無效的JSONata運算式: __error__", - "too-many" : "Switch節點中有太多待定信息" + "too-many": "Switch節點中有太多待定信息" } }, "change": { @@ -588,6 +660,7 @@ } }, "range": { + "range": "range", "label": { "action": "操作", "inputrange": "映射輸入資料", @@ -623,7 +696,8 @@ "firstrow": "第一行包含列名", "output": "輸出", "includerow": "包含列名行", - "newline": "分行符號" + "newline": "分行符號", + "usestrings": "解析數值" }, "placeholder": { "columns": "用逗號分割列名" @@ -654,7 +728,8 @@ "html": { "label": { "select": "選取項", - "output": "輸出" + "output": "輸出", + "in": "in" }, "output": { "html": "選定元素的html內容", @@ -670,7 +745,9 @@ "errors": { "dropped-object": "忽略非物件格式的有效負載", "dropped": "忽略不支援格式的有效負載類型", - "dropped-error": "轉換有效負載失敗" + "dropped-error": "轉換有效負載失敗", + "schema-error": "JSON架構錯誤", + "schema-error-compile": "JSON架構錯誤: 未能編譯架構" }, "label": { "o2j": "對象至JSON", @@ -679,8 +756,8 @@ "property": "屬性", "actions": { "toggle": "JSON字串與物件互轉", - "str":"總是轉為JSON字串", - "obj":"總是轉為JS對象" + "str": "總是轉為JSON字串", + "obj": "總是轉為JS對象" } } }, @@ -702,76 +779,6 @@ "xml_js": "此節點僅處理XML字串或JS物件." } }, - "rpi-gpio": { - "label": { - "gpiopin": "GPIO", - "selectpin": "選擇引腳", - "resistor": "電阻?", - "readinitial": "在部署/重啟時讀取引腳的初始狀態?", - "type": "類型", - "initpin": "初始化引腳狀態?", - "debounce": "去抖動", - "freq": "頻率", - "button": "按鈕", - "pimouse": "Pi滑鼠", - "pikeyboard": "Pi鍵盤", - "left": "左", - "right": "右", - "middle": "中" - }, - "resistor": { - "none": "無", - "pullup": "上拉電阻", - "pulldown": "下拉電阻" - }, - "digout": "數位輸出", - "pwmout": "PWM輸出", - "servo": "伺服輸出", - "initpin0": "初始引腳電平 - 低(0)", - "initpin1": "初始引腳電平 - 高(1)", - "left": "左", - "right": "右", - "middle": "中", - "any": "任何", - "pinname": "引腳", - "alreadyuse": "已被使用", - "alreadyset": "已被設為", - "tip": { - "pin": "正在使用引腳: ", - "in": "提示: 僅接受數位輸入 - 輸出必須為0或1.", - "dig": "提示: 如用數位輸出 - 輸入必須為0或1.", - "pwm": "提示: 如用PWM輸出 - 輸入必須為0至100之間; 如用高頻率可能會比預期佔用更多CPU資源.", - "ser": "提示: 如用伺服輸出 - 輸入必須為0至100之間. 50為中間值." - }, - "types": { - "digout": "數位輸出", - "input": "輸入", - "pullup": "含有上拉電阻的輸入", - "pulldown": "含有下拉電阻的輸入", - "pwmout": "PWM輸出", - "servo": "伺服輸出" - }, - "status": { - "stopped": "已停止", - "closed": "已關閉", - "not-running": "不運行" - }, - "errors": { - "ignorenode": "忽略樹莓派的特定節點", - "version": "版本命令失敗", - "sawpitype": "查看Pi類型", - "libnotfound": "找不到樹莓派RPi.GPIO的python庫", - "alreadyset": "GPIO引腳 __pin__ 已經被設定為類型: __type__", - "invalidpin": "無效GPIO引腳", - "invalidinput": "無效輸入", - "needtobeexecutable": "__command__須為可運行命令", - "mustbeexecutable": "nrgpio須為可運行", - "commandnotfound": "nrgpio命令不存在", - "commandnotexecutable": "nrgpio命令不可運行", - "error": "錯誤: __error__", - "pythoncommandnotfound": "nrpgio python命令未處於運行狀態" - } - }, "file": { "label": { "filename": "檔案名", @@ -783,7 +790,10 @@ "breaklines": "分拆成行", "filelabel": "文件", "sendError": "發生錯誤時發送消息(傳統模式)", - "deletelabel": "刪除 __file__" + "deletelabel": "刪除 __file__", + "encoding": "編碼", + "utf8String": "UTF8字符串", + "binaryBuffer": "二進制buffer" }, "action": { "append": "追加至文件", @@ -801,6 +811,21 @@ "deletedfile": "刪除檔: __file__", "appendedfile": "追加至文件: __file__" }, + "encoding": { + "none": "默認", + "native": "Native", + "unicode": "Unicode", + "japanese": "日本", + "chinese": "中國", + "korean": "韓國", + "taiwan": "臺灣/香港", + "windows": "Windows代碼頁", + "iso": "ISO代碼頁", + "ibm": "IBM代碼頁", + "mac": "Mac代碼頁", + "koi8": "KOI8代碼頁", + "misc": "其它" + }, "errors": { "nofilename": "未指定檔案名", "invaliddelete": "警告:無效刪除。請在配置對話方塊中使用特定的刪除選項", @@ -812,50 +837,53 @@ "tip": "提示: 檔案名應該是絕對路徑,否則它將相對於Node-RED進程的工作目錄。" }, "split": { - "intro":"基於以下類型拆分msg.payload:", - "object":"對象", - "objectSend":"每個鍵值對作為單個消息發送", - "strBuff":"字串 / Buffer", - "array":"陣列", - "splitUsing":"拆分使用", - "splitLength":"固定長度", - "stream":"作為消息流處理", - "addname":" 複製鍵到 " + "split": "split", + "intro": "基於以下類型拆分msg.payload:", + "object": "對象", + "objectSend": "每個鍵值對作為單個消息發送", + "strBuff": "字串 / Buffer", + "array": "陣列", + "splitUsing": "拆分使用", + "splitLength": "固定長度", + "stream": "作為消息流處理", + "addname": " 複製鍵到 " }, - "join":{ - "mode":{ - "mode":"模式", - "auto":"自動", - "merge":"合併序列", - "reduce":"縮減序列", - "custom":"手動" + "join": { + "join": "join", + "mode": { + "mode": "模式", + "auto": "自動", + "merge": "合併序列", + "reduce": "縮減序列", + "custom": "手動" }, - "combine":"合併每個", - "create":"輸出為", - "type":{ - "string":"字串", - "array":"陣列", - "buffer":"Buffer", - "object":"鍵值對對象", - "merged":"合併對象" + "combine": "合併每個", + "completeMessage": "完整的消息", + "create": "輸出為", + "type": { + "string": "字串", + "array": "陣列", + "buffer": "Buffer", + "object": "鍵值對對象", + "merged": "合併對象" }, - "using":"使用此值", - "key":"作為鍵", - "joinedUsing":"合併符號", - "send":"發送資訊:", - "afterCount":"達到一定數量的資訊時", - "count":"數量", - "subsequent":"和每個後續的消息", - "afterTimeout":"第一條消息的若干時間後", - "seconds":"秒", - "complete":"在收到存在msg.complete的消息後", - "tip":"此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", - "too-many" : "join節點中有太多待定信息", + "using": "使用此值", + "key": "作為鍵", + "joinedUsing": "合併符號", + "send": "發送資訊:", + "afterCount": "達到一定數量的資訊時", + "count": "數量", + "subsequent": "和每個後續的消息", + "afterTimeout": "第一條消息的若幹時間後", + "seconds": "秒", + "complete": "在收到存在msg.complete的消息後", + "tip": "此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", + "too-many": "join節點中有太多待定信息", "merge": { - "topics-label":"合併主題", - "topics":"主題", - "topic" : "主題", - "on-change":"當收到一個新主題時發送已合併資訊" + "topics-label": "合併主題", + "topics": "主題", + "topic": "主題", + "on-change": "當收到一個新主題時發送已合併資訊" }, "reduce": { "exp": "Reduce運算式", @@ -868,43 +896,45 @@ "invalid-expr": "無效的JSONata運算式: __error__" } }, - "sort" : { - "target" : "排序屬性", - "seq" : "資訊佇列", - "key" : "鍵值", - "elem" : "元素值", - "order" : "順序", - "ascending" : "昇冪", - "descending" : "降冪", - "as-number" : "作為數值", - "invalid-exp" : "sort節點中存在無效的JSONata運算式", - "too-many" : "sort節點中有太多待定信息", - "clear" : "清空sort節點中的待定資訊" + "sort": { + "sort": "排序", + "target": "排序屬性", + "seq": "資訊佇列", + "key": "鍵值", + "elem": "元素值", + "order": "順序", + "ascending": "昇冪", + "descending": "降冪", + "as-number": "作為數值", + "invalid-exp": "排序節點中存在無效的JSONata運算式", + "too-many": "排序節點中有太多待定信息", + "clear": "清空排序節點中的待定資訊" }, - "batch" : { + "batch": { + "batch": "batch", "mode": { - "label" : "模式", - "num-msgs" : "按指定數量分組", - "interval" : "按時間間隔分組", - "concat" : "按主題分組" + "label": "模式", + "num-msgs": "按指定數量分組", + "interval": "按時間間隔分組", + "concat": "按主題分組" }, "count": { - "label" : "分組數量", - "overlap" : "隊末隊首重疊數量", - "count" : "數量", - "invalid" : "無效的分組數量或重疊數量" + "label": "分組數量", + "overlap": "隊末隊首重疊數量", + "count": "數量", + "invalid": "無效的分組數量或重疊數量" }, "interval": { - "label" : "時間間隔", - "seconds" : "秒", - "empty" : "無數據到達時發送空資訊" + "label": "時間間隔", + "seconds": "秒", + "empty": "無數據到達時發送空資訊" }, "concat": { "topics-label": "主題", - "topic" : "主題" + "topic": "主題" }, - "too-many" : "batch節點中有太多待定信息", - "unexpected" : "未知模式", - "no-parts" : "資訊中沒有parts屬性" + "too-many": "batch節點中有太多待定信息", + "unexpected": "未知模式", + "no-parts": "資訊中沒有parts屬性" } } diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html new file mode 100644 index 000000000..ee4b3bd95 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html @@ -0,0 +1,19 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html new file mode 100644 index 000000000..23f899627 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html @@ -0,0 +1,22 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html new file mode 100644 index 000000000..825bf218e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html @@ -0,0 +1,70 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html new file mode 100644 index 000000000..0d44ce3b1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html @@ -0,0 +1,81 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html new file mode 100644 index 000000000..71ed96087 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html @@ -0,0 +1,78 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html new file mode 100644 index 000000000..4bb2c7f4d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html new file mode 100644 index 000000000..2898ca718 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html new file mode 100644 index 000000000..401af48e3 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html new file mode 100644 index 000000000..9a8638614 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html new file mode 100644 index 000000000..b1559455f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html new file mode 100644 index 000000000..1a46c3690 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html new file mode 100644 index 000000000..4f0491291 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html @@ -0,0 +1,48 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html new file mode 100644 index 000000000..8ea4f87f8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html new file mode 100644 index 000000000..fd97b497a --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html @@ -0,0 +1,133 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html new file mode 100644 index 000000000..cb8e7fa21 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html @@ -0,0 +1,41 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html new file mode 100644 index 000000000..79879076e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html new file mode 100644 index 000000000..ce2531137 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html @@ -0,0 +1,55 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html new file mode 100644 index 000000000..d8e1b5807 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index d2b3060ee..cf897dbb0 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "repository": { "type": "git", @@ -15,30 +15,30 @@ } ], "dependencies": { - "ajv": "6.10.2", + "ajv": "6.12.0", "body-parser": "1.19.0", "cheerio": "0.22.0", "content-type": "1.0.4", "cookie-parser": "1.4.4", "cookie": "0.4.0", "cors": "2.8.5", - "cron": "1.7.2", + "cron": "1.8.2", "denque": "1.4.1", "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", - "https-proxy-agent": "2.2.4", + "https-proxy-agent": "5.0.0", "is-utf8": "0.2.1", "js-yaml": "3.13.1", "media-typer": "1.1.0", "mqtt": "2.18.8", "multer": "1.4.2", - "mustache": "3.0.2", + "mustache": "4.0.0", "on-headers": "1.0.2", "raw-body": "2.4.1", "request": "2.88.0", "ws": "6.2.1", - "xml2js": "0.4.22", - "iconv-lite": "0.5.0" + "xml2js": "0.4.23", + "iconv-lite": "0.5.1" } } diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index fefb24360..448c46861 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,9 +16,9 @@ } ], "dependencies": { - "@node-red/util": "1.0.3", + "@node-red/util": "1.0.4", "semver": "6.3.0", - "uglify-js": "3.6.9", + "uglify-js": "3.8.0", "when": "3.7.8" } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 76e645b92..277099ecc 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -57,6 +57,8 @@ var api = module.exports = { * Sets the current flow configuration * @param {Object} opts * @param {User} opts.user - the user calling the api + * @param {Object} opts.flows - the flow configuration: `{flows: [..], credentials: {}}` + * @param {Object} opts.deploymentType - the type of deployment - "full", "nodes", "flows", "reload" * @param {Object} opts.req - the request to log (optional) * @return {Promise} - the active flow configuration * @memberof @node-red/runtime_flows @@ -83,7 +85,7 @@ var api = module.exports = { return reject(err); } } - apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType); + apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType); } apiPromise.then(function(flowId) { return resolve({rev:flowId}); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index c62895f7f..a834c8c34 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -193,6 +193,7 @@ Node.prototype.emit = function(event, ...args) { */ Node.prototype._emitInput = function(arg) { var node = this; + this.metric("receive", arg); if (node._inputCallback) { // Just one callback registered. try { @@ -448,7 +449,6 @@ Node.prototype.receive = function(msg) { if (!msg._msgid) { msg._msgid = redUtil.generateId(); } - this.metric("receive",msg); this.emit("input",msg); }; 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 322fa2868..c395c0615 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 @@ -420,15 +420,39 @@ function createRootContext() { Object.defineProperties(obj, { get: { value: function(key, storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback() + return; + } return undefined; } }, set: { value: function(key, value, storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback() + return + } } }, keys: { value: function(storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback(); + return; + } return undefined; } } @@ -436,25 +460,48 @@ function createRootContext() { return obj; } -function getContext(localId,flowId,parent) { - var contextId = localId; +/** + * Get a flow-level context object. + * @param {string} flowId [description] + * @param {string} parentFlowId the id of the parent flow. undefined + * @return {object}} the context object + */ +function getFlowContext(flowId,parentFlowId) { + if (contexts.hasOwnProperty(flowId)) { + return contexts[flowId]; + } + var parentContext = contexts[parentFlowId]; + if (!parentContext) { + parentContext = createRootContext(); + contexts[parentFlowId] = parentContext; + // throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId); + } + var newContext = createContext(flowId,undefined,parentContext); + contexts[flowId] = newContext; + return newContext; + +} + +function getContext(nodeId, flowId) { + var contextId = nodeId; if (flowId) { - contextId = localId+":"+flowId; + contextId = nodeId+":"+flowId; } if (contexts.hasOwnProperty(contextId)) { return contexts[contextId]; } - var newContext = createContext(contextId,undefined,parent); + var newContext = createContext(contextId); + if (flowId) { - var node = flows.get(flowId); - var parent = undefined; - if (node && node.type.startsWith("subflow:")) { - parent = node.context().flow; + var flowContext = contexts[flowId]; + if (!flowContext) { + // This is most likely due to a unit test for a node which doesn't + // initialise the flow properly. + // To keep things working, initialise the missing context. + // This *does not happen* in normal node-red operation + flowContext = createContext(flowId,undefined,createRootContext()); + contexts[flowId] = flowContext; } - else { - parent = createRootContext(); - } - var flowContext = getContext(flowId,undefined,parent); Object.defineProperty(newContext, 'flow', { value: flowContext }); @@ -466,6 +513,39 @@ function getContext(localId,flowId,parent) { return newContext; } +// +// function getContext(localId,flowId,parent) { +// var contextId = localId; +// if (flowId) { +// contextId = localId+":"+flowId; +// } +// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId)); +// if (contexts.hasOwnProperty(contextId)) { +// return contexts[contextId]; +// } +// var newContext = createContext(contextId,undefined,parent); +// if (flowId) { +// var node = flows.get(flowId); +// console.log("flows,get",flowId,node&&node.type) +// var parent = undefined; +// if (node && node.type.startsWith("subflow:")) { +// parent = node.context().flow; +// } +// else { +// parent = createRootContext(); +// } +// var flowContext = getContext(flowId,undefined,parent); +// Object.defineProperty(newContext, 'flow', { +// value: flowContext +// }); +// } +// Object.defineProperty(newContext, 'global', { +// value: contexts['global'] +// }) +// contexts[contextId] = newContext; +// return newContext; +// } + function deleteContext(id,flowId) { if(!hasConfiguredStore){ // only delete context if there's no configured storage. @@ -517,6 +597,7 @@ module.exports = { load: load, listStores: listStores, get: getContext, + getFlowContext:getFlowContext, delete: deleteContext, clean: clean, close: close diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index db15e8eb5..5fa77d505 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -18,6 +18,7 @@ var clone = require("clone"); var redUtil = require("@node-red/util").util; var flowUtil = require("./util"); var events = require("../../events"); +const context = require('../context'); var Subflow; var Log; @@ -54,6 +55,8 @@ class Flow { this.catchNodes = []; this.statusNodes = []; this.path = this.id; + // Ensure a context exists for this flow + this.context = context.getFlowContext(this.id,this.parent.id); } /** diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index c0bc63dd2..370f5263e 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -16,7 +16,7 @@ const clone = require("clone"); const Flow = require('./Flow').Flow; - +const context = require('../context'); const util = require("util"); const redUtil = require("@node-red/util").util; @@ -246,6 +246,10 @@ class Subflow extends Flow { this.node.on("input", function(msg) { this.send(msg);}); this.node.on("close", function() { this.status({}); }) this.node.status = status => this.parent.handleStatus(this.node,status); + // Create a context instance + console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) + this._context = context.get(this._alias||this.id,this.z); + this.node._updateWires = this.node.updateWires; @@ -483,7 +487,7 @@ function createNodeInSubflow(subflowInstanceId, def) { * properties in the nodes object to reference the new node ids. * This handles: * - node.wires, - * - node.scope of Catch and Status nodes, + * - node.scope of Complete, Catch and Status nodes, * - node.XYZ for any property where XYZ is recognised as an old property * @param {[type]} nodes [description] * @param {[type]} nodeMap [description] @@ -506,7 +510,7 @@ function remapSubflowNodes(nodes,nodeMap) { } } } - if ((node.type === 'catch' || node.type === 'status') && node.scope) { + if ((node.type === 'complete' || node.type === 'catch' || node.type === 'status') && node.scope) { node.scope = node.scope.map(function(id) { return nodeMap[id]?nodeMap[id].id:"" }) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js index 981ed7173..16166fbc1 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js @@ -106,15 +106,20 @@ function load(forceStart) { // This is a force reload from the API - disable safeMode delete settings.safeMode; } - return setFlows(null,"load",false,forceStart); + return setFlows(null,null,"load",false,forceStart); } /* * _config - new node array configuration + * _credentials - new credentials configuration (optional) * type - full/nodes/flows/load (default full) * muteLog - don't emit the standard log messages (used for individual flow api) */ -function setFlows(_config,type,muteLog,forceStart) { +function setFlows(_config,_credentials,type,muteLog,forceStart) { + if (typeof _credentials === "string") { + type = _credentials; + _credentials = null; + } type = type||"full"; if (settings.safeMode) { if (type !== "load") { @@ -155,16 +160,27 @@ function setFlows(_config,type,muteLog,forceStart) { delete newFlowConfig.allNodes[id].credentials; } } + var credsDirty; - // Allow the credential store to remove anything no longer needed - credentials.clean(config); + if (_credentials) { + // A full set of credentials have been provided. Use those instead + configSavePromise = credentials.load(_credentials); + credsDirty = true; + } else { + // Allow the credential store to remove anything no longer needed + credentials.clean(config); - // Remember whether credentials need saving or not - var credsDirty = credentials.dirty(); + // Remember whether credentials need saving or not + var credsDirty = credentials.dirty(); + + configSavePromise = Promise.resolve(); + } // Get the latest credentials and ask storage to save them (if needed) // as well as the new flow configuration. - configSavePromise = credentials.export().then(function(creds) { + configSavePromise = configSavePromise.then(function() { + return credentials.export() + }).then(function(creds) { var saveConfig = { flows: config, credentialsDirty:credsDirty, @@ -515,7 +531,7 @@ function addFlow(flow) { var newConfig = clone(activeConfig.flows); newConfig = newConfig.concat(nodes); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); return flow.id; }); @@ -646,7 +662,7 @@ function updateFlow(id,newFlow) { } newConfig = newConfig.concat(nodes); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"})); }) } @@ -668,7 +684,7 @@ function removeFlow(id) { return node.z !== id && node.id !== id; }); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); }); } diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js index da0e1f94f..f72ce1e1e 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js @@ -25,73 +25,54 @@ var settings; var libDir; var libFlowsDir; - -function getFileMeta(root,path) { - var fn = fspath.join(root,path); - var fd = fs.openSync(fn,"r"); +function getFileMeta(root, path) { + var fn = fspath.join(root, path); + var fd = fs.openSync(fn, 'r'); var size = fs.fstatSync(fd).size; var meta = {}; var read = 0; var length = 10; - var remaining = ""; + var remaining = Buffer.alloc(0); var buffer = Buffer.alloc(length); - while(read < size) { - read+=fs.readSync(fd,buffer,0,length); - var data = remaining+buffer.toString(); - var parts = data.split("\n"); - remaining = parts.splice(-1); - for (var i=0;i 0?"\n":"")+parts[i]; - } - } - if (!scanning) { - body += remaining; - } - } else { - read += thisRead; - body += buffer.slice(0,thisRead).toString(); + for (var i = 0; i < parts.length; i++) { + if (! /^\/\/ \w+: /.test(parts[i]) || !scanning) { + body += (body.length > 0 ? '\n' : '') + parts[i]; + scanning = false; } } - fs.closeSync(fd); return body; } + function getLibraryEntry(type,path) { var root = fspath.join(libDir,type); var rootPath = fspath.join(libDir,type,path); diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index b9f114698..d812c05f2 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -41,6 +41,9 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_connection_failed"; } else if (/Connection timed out/i.test(stderr)) { err.code = "git_connection_failed"; + } else if(/Host key verification failed/i.test(stderr)) { + // TODO: handle host key verification errors separately + err.code = "git_host_key_verification_failed"; } else if (/fatal: could not read/i.test(stderr)) { // Username/Password err.code = "git_auth_failed"; @@ -48,9 +51,6 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_auth_failed"; } else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_auth_failed"; - } else if(/Host key verification failed/i.test(stderr)) { - // TODO: handle host key verification errors separately - err.code = "git_auth_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) { diff --git a/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json new file mode 100644 index 000000000..b96d87a6e --- /dev/null +++ b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json @@ -0,0 +1,174 @@ +{ + "runtime": { + "welcome": "歡迎使用Node-RED", + "version": "__component__ 版本: __version__", + "unsupported_version": "__component__的不受支持的版本。要求: __requires__ 找到: __version__", + "paths": { + "settings": "設置文件 : __path__", + "httpStatic": "HTTP Static : __path__" + } + }, + + "server": { + "loading": "加載控制板節點", + "palette-editor": { + "disabled": "控制板編輯器已禁用:用戶設置", + "npm-not-found": "控制板編輯器已禁用:找不到npm命令", + "npm-too-old": "控制板編輯器已禁用: npm版本太舊。需要版本npm >= 3.x" + }, + "errors": "無法註冊__count__節點類型", + "errors_plural": "無法註冊__count__個節點類型", + "errors-help": "使用-v運行以獲取詳細信息", + "missing-modules": "缺少節點模組:", + "node-version-mismatch": "無法在此版本上加載節點模組。要求:__ version__", + "type-already-registered": "'__type__'已由模組__module__註冊", + "removing-modules": "從配置中刪除模組", + "added-types": "添加的節點類型:", + "removed-types": "刪除的節點類型:", + "install": { + "invalid": "無效的模組名稱", + "installing": "安裝模組:__ name__,版本:__ version__", + "installed": "已安裝的模組:__ name__", + "install-failed": "安裝失敗", + "install-failed-long": "模組__name__安裝失敗:", + "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) 未發現版本", + "uninstalling": "卸載模組: __name__", + "uninstall-failed": "卸載失敗", + "uninstall-failed-long": "卸載模組__name__失敗:", + "uninstalled": "卸載模組: __name__" + }, + "unable-to-listen": "無法在__listenpath__上收聽", + "port-in-use": "錯誤: 端口正在使用中", + "uncaught-exception": "未捕獲的異常:", + "admin-ui-disabled": "管理員界面已禁用", + "now-running": "服務器現在在__listenpath__上運行", + "failed-to-start": "無法啟動服務器:", + "headless-mode": "在headless模式下運行", + "httpadminauth-deprecated": "不建議使用httpAdminAuth。請改用adminAuth" + }, + + "api": { + "flows": { + "error-save": "保存流程錯誤: __message__", + "error-reload": "重載流程錯誤: __message__" + }, + "library": { + "error-load-entry": "加載庫條目'__path__'時出錯:__message__", + "error-save-entry": "保存庫條目'__path__'時出錯:__ message__", + "error-load-flow": "加載流程'__path__'時出錯:__ message__", + "error-save-flow": "保存流'__path__'時出錯:__ message__" + }, + "nodes": { + "enabled": "啟用的節點類型:", + "disabled": "禁用的節點類型:", + "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__", + "not-registered": "證書類型'__type__'未註冊", + "system-key-warning": "\n\n---------------------------------------------------------------------\n您的流程證書文件是使用系統生成的密鑰加密的。\n\n如果系統生成的密鑰由於任何原因丟失,則您的證書文件將無法恢復,您將必須刪除它並重新輸入您的證書。\n\n您應該使用您的設置文件中的'credentialSecret'選項設置自己的密鑰。然後,下次部署更改時,Node-RED將使用選擇的密鑰重新加密您的證書文件。\n---------------------------------------------------------------------\n" + }, + "flows": { + "safe-mode": "流程在安全模式下停止。部署開始。", + "registered-missing": "缺少註冊的類型:__ type__", + "error": "錯誤加載流程:__ message__", + "starting-modified-nodes": "啟動修改的節點", + "starting-modified-flows": "啟動修改的流程", + "starting-flows": "啟動流程", + "started-modified-nodes": "修改的節點已啟動", + "started-modified-flows": "修改的流程已啟動", + "started-flows": "流程已啟動", + "stopping-modified-nodes": "停止修改的節點", + "stopping-modified-flows": "停止修改的流程", + "stopping-flows": "停止流程", + "stopped-modified-nodes": "修改的節點已停止", + "stopped-modified-flows": "修改的流程已停止", + "stopped-flows": "流程已停止", + "stopped": "已停止", + "stopping-error": "錯誤停止節點:__ message__", + "added-flow": "流程已添加: __label__", + "updated-flow": "流程已更新: __label__", + "removed-flow": "流程已移除: __label__", + "missing-types": "等待缺少的類型被註冊:", + "missing-type-provided": " - __type__ (由npm模組__module__提供)", + "missing-type-install-1": "要安裝所有缺少的模組,請運行:", + "missing-type-install-2": "在目錄中:" + }, + "flow": { + "unknown-type": "未知類型: __type__", + "missing-types": "缺少類型", + "error-loop": "郵件已超過最大捕獲數" + }, + "index": { + "unrecognised-id": "無法識別的ID: __id__", + "type-in-use": "使用中的類型: __msg__", + "unrecognised-module": "無法識別的模組: __module__" + }, + "registry": { + "localfilesystem": { + "module-not-found": "找不到模組:'__module__'" + } + } + }, + + "storage": { + "index": { + "forbidden-flow-name": "禁止流程名稱" + }, + "localfilesystem": { + "user-dir": "用戶目錄: __path__", + "flows-file": "流程文件: __path__", + "create": "創建新__type__文件", + "empty": "現有__type__文件為空", + "invalid": "現有__type__文件為無效json", + "restore": "恢復__type__文件備份:__path__", + "restore-fail": "恢復__type__文件備份失敗:__ message__", + "fsync-fail": "將文件__path__刷新到磁盤失敗:__message__", + "projects": { + "changing-project": "設置活動項目:__ project__", + "active-project": "活動項目:__ project__", + "project-not-found": "找不到項目:__ project__", + "no-active-project": "沒有活動的項目:使用默認流文件", + "disabled": "項目已禁用:editorTheme.projects.enabled = false", + "disabledNoFlag": "項目已禁用:設置editorTheme.projects.enabled = true來啟用", + "git-not-found": "項目已禁用:找不到git命令", + "git-version-old": "項目已禁用:不支持的git __version__。 需要的git版本為2.x", + "summary": "一個Node-RED項目", + "readme": "### 關於\n\n這是您項目的README.md文件。它可以幫助用戶了解您的項目,如何使用它以及他們可能需要知道的其他任何信息。" + } + } + }, + + "context": { + "log-store-init": "上下文儲存: '__name__' [__info__]", + "error-loading-module": "加載上下文存儲時出錯: __message__", + "error-loading-module2": "加載上下文存儲時出錯 '__module__': __message__", + "error-module-not-defined": "上下文存儲庫'__storage__'缺少“模組”選項", + "error-invalid-module-name": "無效的上下文存儲名稱:'__ name__'", + "error-invalid-default-module": "無效的默認的上下文存儲庫: '__storage__'", + "unknown-store": "指定了未知的上下文存儲庫'__name__'。 使用默認存儲庫。", + "localfilesystem": { + "error-circular": "上下文__scope__包含無法保留的循環引用", + "error-write": "編寫上下文時出錯:__ message__" + } + } +} diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 0d03b355b..50f9fb264 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.0.3", - "@node-red/util": "1.0.3", + "@node-red/registry": "1.0.4", + "@node-red/util": "1.0.4", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index 45a33be14..3e35f7fde 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -558,15 +558,19 @@ function evaluateNodeProperty(value, type, node, msg, callback) { */ function prepareJSONataExpression(value,node) { var expr = jsonata(value); - expr.assign('flowContext',function(val) { - return node.context().flow.get(val); + expr.assign('flowContext',function(val, store) { + return node.context().flow.get(val, store); }); - expr.assign('globalContext',function(val) { - return node.context().global.get(val); + expr.assign('globalContext',function(val, store) { + return node.context().global.get(val, store); }); expr.assign('env', function(name) { var val = getSetting(node, name); - return (val ? val : ""); + if (typeof val !== 'undefined') { + return val; + } else { + return "" + } }) expr.registerFunction('clone', cloneMessage, '<(oa)-:o>'); expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value); diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 411c35a7c..9beb6189b 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "repository": { "type": "git", @@ -18,7 +18,7 @@ "clone": "2.1.2", "i18next": "15.1.2", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.0", + "jsonata": "1.8.1", "when": "3.7.8" } } diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 618480400..2b7116f64 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.3", + "version": "1.0.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.0.3", - "@node-red/runtime": "1.0.3", - "@node-red/util": "1.0.3", - "@node-red/nodes": "1.0.3", + "@node-red/editor-api": "1.0.4", + "@node-red/runtime": "1.0.4", + "@node-red/util": "1.0.4", + "@node-red/nodes": "1.0.4", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 960cb6d17..c5e4355e1 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -243,7 +243,7 @@ module.exports = { // palette. If a node's category is not in the list, the category will get // added to the end of the palette. // If not set, the following default order is used: - //paletteCategories: ['subflows','flow','input','output','function','parser','social','mobile','storage','analysis','advanced'], + //paletteCategories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], // Configure the logging output logging: { diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index a35859c5f..3b484a58c 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -15,22 +15,40 @@ **/ var idMap = { - // input + // common "inject": ".red-ui-palette-node[data-palette-type='inject']", - "httpIn": ".red-ui-palette-node[data-palette-type='http in']", - "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", - // output "debug": ".red-ui-palette-node[data-palette-type='debug']", - "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", - "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", + "complete": ".red-ui-palette-node[data-palette-type='complete']", + "catch": ".red-ui-palette-node[data-palette-type='catch']", + "status": ".red-ui-palette-node[data-palette-type='status']", + "comment": ".red-ui-palette-node[data-palette-type='comment']", // function "function": ".red-ui-palette-node[data-palette-type='function']", - "template": ".red-ui-palette-node[data-palette-type='template']", + "switch": ".red-ui-palette-node[data-palette-type='switch']", "change": ".red-ui-palette-node[data-palette-type='change']", "range": ".red-ui-palette-node[data-palette-type='range']", + "template": ".red-ui-palette-node[data-palette-type='template']", + "delay": ".red-ui-palette-node[data-palette-type='delay']", + "trigger": ".red-ui-palette-node[data-palette-type='trigger']", + "exec": ".red-ui-palette-node[data-palette-type='exec']", + // network + "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", + "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", + "httpIn": ".red-ui-palette-node[data-palette-type='http in']", + "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", "httpRequest": ".red-ui-palette-node[data-palette-type='http request']", + "websocketIn": ".red-ui-palette-node[data-palette-type='websocket in']", + "websocketOut": ".red-ui-palette-node[data-palette-type='websocket out']", + // sequence + "split": ".red-ui-palette-node[data-palette-type='split']", + "join": ".red-ui-palette-node[data-palette-type='join']", + "batch": ".red-ui-palette-node[data-palette-type='batch']", + // parser + "csv": ".red-ui-palette-node[data-palette-type='csv']", "html": ".red-ui-palette-node[data-palette-type='html']", "json": ".red-ui-palette-node[data-palette-type='json']", + "xml": ".red-ui-palette-node[data-palette-type='xml']", + "yaml": ".red-ui-palette-node[data-palette-type='yaml']", // storage "fileIn": ".red-ui-palette-node[data-palette-type='file in']", }; diff --git a/test/editor/pageobjects/editor/workspace_page.js b/test/editor/pageobjects/editor/workspace_page.js index ddf0a1c60..92a725dac 100644 --- a/test/editor/pageobjects/editor/workspace_page.js +++ b/test/editor/pageobjects/editor/workspace_page.js @@ -42,7 +42,7 @@ function addNode(type, x, y) { } } browser.waitForVisible('#red-ui-palette-search'); - browser.setValue('//*[@id="red-ui-palette-search"]/div/input', type.replace(/([A-Z])/g,' $1').toLowerCase()); + browser.setValue('//*[@id="red-ui-palette-search"]/div/form/input', type.replace(/([A-Z])/g, ' $1').toLowerCase()); browser.pause(300); browser.waitForVisible(palette.getId(type)); browser.moveToObject(palette.getId(type)); @@ -66,8 +66,8 @@ function deleteAllNodes() { function deploy() { browser.call(function () { - return when.promise(function(resolve, reject) { - events.on("runtime-event", function(event) { + return when.promise(function (resolve, reject) { + events.on("runtime-event", function (event) { if (event.id === 'runtime-deploy') { events.removeListener("runtime-event", arguments.callee); resolve(); diff --git a/test/editor/pageobjects/nodes/core/common/21-debug_page.js b/test/editor/pageobjects/nodes/core/common/21-debug_page.js index 602d4d542..990ed6cd9 100644 --- a/test/editor/pageobjects/nodes/core/common/21-debug_page.js +++ b/test/editor/pageobjects/nodes/core/common/21-debug_page.js @@ -18,8 +18,6 @@ var util = require("util"); var nodePage = require("../../node_page"); -var keyPage = require("../../../util/key_page"); - function debugNode(id) { nodePage.call(this, id); } @@ -33,6 +31,8 @@ debugNode.prototype.setOutput = function (complete) { // Select the "msg" type. browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options")][1]/a[1]'); // Input the path in msg. + browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input'); + browser.keys(Array('payload'.length).fill('Backspace')); browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete); } else { // Select the "complete msg object" type. diff --git a/test/editor/pageobjects/nodes/core/common/24-complete_page.js b/test/editor/pageobjects/nodes/core/common/24-complete_page.js new file mode 100644 index 000000000..558ee709a --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/24-complete_page.js @@ -0,0 +1,47 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function completeNode(id) { + nodePage.call(this, id); +} + +util.inherits(completeNode, nodePage); + +completeNode.prototype.setScope = function (scope) { + if (scope) { + browser.clickWithWait('//*[@id="node-input-complete-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = completeNode; diff --git a/test/editor/pageobjects/nodes/core/common/25-catch_page.js b/test/editor/pageobjects/nodes/core/common/25-catch_page.js new file mode 100644 index 000000000..89bcb7f1e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/25-catch_page.js @@ -0,0 +1,48 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function catchNode(id) { + nodePage.call(this, id); +} + +util.inherits(catchNode, nodePage); + +catchNode.prototype.setScope = function (scope) { + if (scope) { + browser.selectWithWait('#node-input-scope-select', 'target'); + browser.clickWithWait('//*[@id="node-input-catch-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = catchNode; diff --git a/test/editor/pageobjects/nodes/core/common/25-status_page.js b/test/editor/pageobjects/nodes/core/common/25-status_page.js new file mode 100644 index 000000000..6de0fcde7 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/25-status_page.js @@ -0,0 +1,48 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function statusNode(id) { + nodePage.call(this, id); +} + +util.inherits(statusNode, nodePage); + +statusNode.prototype.setScope = function (scope) { + if (scope) { + browser.selectWithWait('#node-input-scope-select', 'target'); + browser.clickWithWait('//*[@id="node-input-status-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = statusNode; diff --git a/test/editor/pageobjects/nodes/core/common/90-comment_page.js b/test/editor/pageobjects/nodes/core/common/90-comment_page.js new file mode 100644 index 000000000..2687dacfe --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/90-comment_page.js @@ -0,0 +1,27 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function commentNode(id) { + nodePage.call(this, id); +} + +util.inherits(commentNode, nodePage); + +module.exports = commentNode; diff --git a/test/editor/pageobjects/nodes/core/function/10-switch_page.js b/test/editor/pageobjects/nodes/core/function/10-switch_page.js new file mode 100644 index 000000000..a04014063 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/10-switch_page.js @@ -0,0 +1,234 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function switchNode(id) { + nodePage.call(this, id); +} + +util.inherits(switchNode, nodePage); + +var vtType = { + "msg": 1, + "flow": 2, + "global": 3, + "str": 4, + "num": 5, + "jsonata": 6, + "env": 7, + "prev": 8 +}; + +function setT(t, index) { + browser.selectWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', t); +} + +function setV(v, vt, index) { + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', v); + } +} + +function setBetweenV(v, vt, v2, v2t, index) { + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } + if (v2t) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[v2t] + ']'); + } + if (v2) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/div[1]/input', v2); + } +} + +function setSequenceV(v, vt, index) { + var sType = { + "flow": 1, + "global": 2, + "num": 3, + "jsonata": 4, + "env": 5, + }; + + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } +} + +switchNode.prototype.ruleEqual = function (v, vt, index) { + index = index || 1; + setT('eq', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleNotEqual = function (v, vt, index) { + index = index || 1; + setT('neq', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleLessThan = function (v, vt, index) { + index = index || 1; + setT('lt', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleLessThanOrEqual = function (v, vt, index) { + index = index || 1; + setT('lte', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleGreaterThan = function (v, vt, index) { + index = index || 1; + setT('gt', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleGreaterThanOrEqual = function (v, vt, index) { + index = index || 1; + setT('gte', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleHasKey = function (v, vt, index) { + index = index || 1; + setT('hask', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleIsBetween = function (v, vt, v2, v2t, index) { + index = index || 1; + setT('btwn', index); + setBetweenV(v, vt, v2, v2t, index); +} + +switchNode.prototype.ruleContains = function (v, vt, index) { + index = index || 1; + setT('cont', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleMatchesRegex = function (v, vt, index) { + index = index || 1; + setT('regex', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleIsTrue = function (index) { + index = index || 1; + setT('true', index); +} + +switchNode.prototype.ruleIsFalse = function (index) { + index = index || 1; + setT('false', index); +} + +switchNode.prototype.ruleIsNull = function (index) { + index = index || 1; + setT('null', index); +} + +switchNode.prototype.ruleIsNotNull = function (index) { + index = index || 1; + setT('nnull', index); +} + +switchNode.prototype.ruleIsOfType = function (t, index) { + index = index || 1; + setT('istype', index); + + var tType = { + "str": 1, + "num": 2, + "boolean": 3, + "array": 4, + "buffer": 5, + "object": 6, + "json": 7, + "undefined": 8, + "null": 9 + }; + + if (t) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + tType[t] + ']'); + } +} + +switchNode.prototype.ruleIsEmpty = function (index) { + index = index || 1; + setT('empty', index); +} + +switchNode.prototype.ruleIsNotEmpty = function (index) { + index = index || 1; + setT('nempty', index); +} + +switchNode.prototype.ruleHead = function (v, vt, index) { + index = index || 1; + setT('head', index); + setSequenceV(v, vt, index); +} + +switchNode.prototype.ruleIndexBetween = function (v, vt, v2, v2t, index) { + index = index || 1; + setT('index', index); + setBetweenV(v, vt, v2, v2t, index); +} + +switchNode.prototype.ruleTail = function (v, vt, index) { + index = index || 1; + setT('tail', index); + setSequenceV(v, vt, index); +} + +switchNode.prototype.ruleJSONataExp = function (v, index) { + index = index || 1; + setT('jsonata_exp', index); + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } +} + +switchNode.prototype.ruleOtherwise = function (index) { + index = index || 1; + setT('else', index); +} + +switchNode.prototype.addRule = function () { + browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a'); +} + +module.exports = switchNode; diff --git a/test/editor/pageobjects/nodes/core/function/15-change_page.js b/test/editor/pageobjects/nodes/core/function/15-change_page.js index 8e72afe38..eb26f48aa 100644 --- a/test/editor/pageobjects/nodes/core/function/15-change_page.js +++ b/test/editor/pageobjects/nodes/core/function/15-change_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function changeNode(id) { nodePage.call(this, id); @@ -51,41 +51,82 @@ function setT(t, index) { // It is better to create a function whose input value is the object type in the future, changeNode.prototype.ruleSet = function (p, pt, to, tot, index) { index = index || 1; - setT("set", 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 - 1) + 1; - var ptXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + ptType[pt] + ']'; - browser.clickWithWait(ptXPath); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); } if (p) { browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); } if (tot) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]'); - var num = 5 * (index - 1) + 2; - var totXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + totType[tot] + ']'; - browser.clickWithWait(totXPath); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[tot] + ']'); } if (to) { browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to); } } -changeNode.prototype.ruleDelete = function (index) { +changeNode.prototype.ruleChange = function (p, pt, from, fromt, to, tot, index) { index = index || 1; - setT("delete", index); + setT('change', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } + if (fromt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (from) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/div[1]/input', from); + } + if (tot) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (to) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/div[1]/input', to); + } } -changeNode.prototype.ruleMove = function (p, to, index) { +changeNode.prototype.ruleDelete = function (p, pt, index) { index = index || 1; - setT("move", index); - browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); - browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); + setT('delete', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } +} + +changeNode.prototype.ruleMove = function (p, pt, to, tot, index) { + index = index || 1; + setT('move', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } + if (tot) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (to) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); + } } changeNode.prototype.addRule = function () { - browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a'); + browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a'); } module.exports = changeNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js b/test/editor/pageobjects/nodes/core/function/89-delay_page.js similarity index 71% rename from test/editor/pageobjects/nodes/core/network/10-mqttin_page.js rename to test/editor/pageobjects/nodes/core/function/89-delay_page.js index 31b909116..3604beb67 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js +++ b/test/editor/pageobjects/nodes/core/function/89-delay_page.js @@ -18,18 +18,14 @@ var util = require("util"); var nodePage = require("../../node_page"); -function mqttInNode(id) { +function delayNode(id) { nodePage.call(this, id); } -util.inherits(mqttInNode, nodePage); +util.inherits(delayNode, nodePage); -mqttInNode.prototype.setTopic = function (topic) { - browser.setValue('#node-input-topic', topic); +delayNode.prototype.setTimeout = function (timeout) { + browser.setValue('//*[@id="node-input-timeout"]', timeout); } -mqttInNode.prototype.setQoS = function (qos) { - browser.selectWithWait('#node-input-qos', qos); -} - -module.exports = mqttInNode; +module.exports = delayNode; diff --git a/test/editor/pageobjects/nodes/core/function/89-trigger_page.js b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js new file mode 100644 index 000000000..5d24de380 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js @@ -0,0 +1,83 @@ +/** + * 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 util = require("util"); + +var nodePage = require("../../node_page"); + +function triggerNode(id) { + nodePage.call(this, id); +} + +util.inherits(triggerNode, nodePage); + +triggerNode.prototype.setSend = function (send, sendt) { + var sendType = { + "flow": 1, + "global": 2, + "str": 3, + "num": 4, + "bool": 5, + "json": 6, + "bin": 7, + "date": 8, + "env": 9, + "pay": 10, + "nul": 11 + }; + + if (sendt) { + browser.clickWithWait('//*[@id="dialog-form"]/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sendType[sendt] + ']'); + } + if (send) { + browser.setValue('//*[@id="dialog-form"]/div[1]/div/div[1]/input', send); + } +} + +triggerNode.prototype.setDuration = function (duration, units) { + browser.setValue('//*[@id="node-input-duration"]', duration); + if (units) { + browser.selectWithWait('//*[@id="node-input-units"]', units); + } +} + +triggerNode.prototype.setThenSend = function (thenSend, thenSendt) { + var thenSendType = { + "flow": 1, + "global": 2, + "str": 3, + "num": 4, + "bool": 5, + "json": 6, + "bin": 7, + "date": 8, + "env": 9, + "pay": 10, + "payl": 11, + "nul": 12 + }; + + if (thenSendt) { + browser.clickWithWait('//*[@id="dialog-form"]/div[5]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + thenSendType[thenSendt] + ']'); + } + if (thenSend) { + browser.setValue('//*[@id="dialog-form"]/div[5]/div/div[1]/input', thenSend); + } +} + +module.exports = triggerNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js b/test/editor/pageobjects/nodes/core/function/90-exec_page.js similarity index 66% rename from test/editor/pageobjects/nodes/core/network/10-mqttout_page.js rename to test/editor/pageobjects/nodes/core/function/90-exec_page.js index 783d87b55..69b8b6c9a 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js +++ b/test/editor/pageobjects/nodes/core/function/90-exec_page.js @@ -18,18 +18,20 @@ var util = require("util"); var nodePage = require("../../node_page"); -function mqttOutNode(id) { +function execNode(id) { nodePage.call(this, id); } -util.inherits(mqttOutNode, nodePage); +util.inherits(execNode, nodePage); -mqttOutNode.prototype.setTopic = function(topic) { - browser.setValue('#node-input-topic', topic); +execNode.prototype.setCommand = function (command) { + browser.setValue('//*[@id="node-input-command"]', command); } -mqttOutNode.prototype.setRetain = function (retain) { - browser.selectWithWait('#node-input-retain', retain); +execNode.prototype.setAppend = function (checkbox) { + if (browser.isSelected('#node-input-addpay') !== checkbox) { + browser.click('#node-input-addpay'); + } } -module.exports = mqttOutNode; \ No newline at end of file +module.exports = execNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js similarity index 52% rename from test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js rename to test/editor/pageobjects/nodes/core/network/10-mqtt_page.js index c7cdc90c5..4bdd92336 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js +++ b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js @@ -14,27 +14,61 @@ * limitations under the License. **/ -function setServer(broker, port) { +var util = require("util"); + +var nodePage = require("../../node_page"); + +var mqttBrokerNode = {}; + +mqttBrokerNode.setServer = function (broker, port) { browser.setValue('#node-config-input-broker', broker); port = port ? port : 1883; browser.setValue('#node-config-input-port', port); -} +}; -function clickOk() { +mqttBrokerNode.clickOk = function () { browser.clickWithWait('#node-config-dialog-ok'); // Wait until an config dialog closes. browser.waitForVisible('#node-config-dialog-ok', 10000, true); -} +}; -function edit() { +mqttBrokerNode.edit = function () { browser.waitForVisible('#node-input-lookup-broker'); browser.click('#node-input-lookup-broker'); // Wait until a config dialog opens. browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +function mqttInNode(id) { + nodePage.call(this, id); } -module.exports = { - setServer: setServer, - clickOk: clickOk, - edit: edit +util.inherits(mqttInNode, nodePage); + +mqttInNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); }; + +mqttInNode.prototype.setQoS = function (qos) { + browser.selectWithWait('#node-input-qos', qos); +}; + +mqttInNode.prototype.mqttBrokerNode = mqttBrokerNode; +module.exports.mqttInNode = mqttInNode; + +function mqttOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(mqttOutNode, nodePage); + +mqttOutNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); +}; + +mqttOutNode.prototype.setRetain = function (retain) { + browser.selectWithWait('#node-input-retain', retain); +}; + +mqttOutNode.prototype.mqttBrokerNode = mqttBrokerNode; +module.exports.mqttOutNode = mqttOutNode; diff --git a/test/editor/pageobjects/nodes/core/network/22-websocket_page.js b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js new file mode 100644 index 000000000..8f7dc261e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js @@ -0,0 +1,93 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +var websocketListenerNode = {}; + +websocketListenerNode.setPath = function (path) { + browser.setValue('#node-config-input-path', path); +}; + +websocketListenerNode.setSendReceive = function (wholemsg) { + browser.selectWithWait('#node-config-input-wholemsg', wholemsg); +}; + +websocketListenerNode.clickOk = function () { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 10000, true); +}; + +websocketListenerNode.edit = function () { + browser.waitForVisible('#node-input-lookup-server'); + browser.click('#node-input-lookup-server'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +var websocketClientNode = {}; + +websocketClientNode.setPath = function (path) { + browser.setValue('#node-config-input-path', path); +}; + +websocketClientNode.setSendReceive = function (wholemsg) { + browser.selectWithWait('#node-config-input-wholemsg', wholemsg); +}; + +websocketClientNode.clickOk = function () { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 10000, true); +}; + +websocketClientNode.edit = function () { + browser.waitForVisible('#node-input-lookup-client'); + browser.click('#node-input-lookup-client'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +function websocketInNode(id) { + nodePage.call(this, id); +} + +util.inherits(websocketInNode, nodePage); + +websocketInNode.prototype.setType = function (type) { + browser.selectWithWait('#node-input-mode', type); +}; + +websocketInNode.prototype.websocketListenerNode = websocketListenerNode; +websocketInNode.prototype.websocketClientNode = websocketClientNode; +module.exports.websocketInNode = websocketInNode; + +function websocketOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(websocketOutNode, nodePage); + +websocketOutNode.prototype.setType = function (type) { + browser.selectWithWait('#node-input-mode', type); +}; + +websocketOutNode.prototype.websocketListenerNode = websocketListenerNode; +websocketOutNode.prototype.websocketClientNode = websocketClientNode; +module.exports.websocketOutNode = websocketOutNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js new file mode 100644 index 000000000..e4bc9502c --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js @@ -0,0 +1,51 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function csvNode(id) { + nodePage.call(this, id); +} + +util.inherits(csvNode, nodePage); + +csvNode.prototype.setColumns = function (columns) { + browser.setValue('#node-input-temp', columns); +} + +csvNode.prototype.setSkipLines = function (skip) { + browser.setValue('#node-input-skip', skip); +} + +csvNode.prototype.setFirstRow4Names = function (checkbox) { + if (browser.isSelected('#node-input-hdrin') !== checkbox) { + browser.click('#node-input-hdrin'); + } +} + +csvNode.prototype.setOutput = function (output) { + browser.selectWithWait('#node-input-multi', output); +} + +csvNode.prototype.setIncludeRow = function (checkbox) { + if (browser.isSelected('#node-input-hdrout') !== checkbox) { + browser.click('#node-input-hdrout'); + } +} + +module.exports = csvNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js index 9c89bc8e2..243e4c905 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function htmlNode(id) { nodePage.call(this, id); @@ -24,7 +24,7 @@ function htmlNode(id) { util.inherits(htmlNode, nodePage); -htmlNode.prototype.setSelector = function(tag) { +htmlNode.prototype.setSelector = function (tag) { browser.setValue('#node-input-tag', tag); } diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js index 10a7e648f..e0b31dd36 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function jsonNode(id) { nodePage.call(this, id); @@ -28,8 +28,8 @@ jsonNode.prototype.setAction = function (action) { browser.setValue('node-input-action', action); } -jsonNode.prototype.setProperty = function(property) { - browser.setValue('//*[@id="dialog-form"]/div[2]/div/div/input', property); +jsonNode.prototype.setProperty = function (property) { + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); } module.exports = jsonNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js new file mode 100644 index 000000000..696ec59cb --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js @@ -0,0 +1,35 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function xmlNode(id) { + nodePage.call(this, id); +} + +util.inherits(xmlNode, nodePage); + +xmlNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +xmlNode.prototype.setProperty = function (property) { + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); +} + +module.exports = xmlNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js new file mode 100644 index 000000000..1002f3eb4 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js @@ -0,0 +1,35 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function yamlNode(id) { + nodePage.call(this, id); +} + +util.inherits(yamlNode, nodePage); + +yamlNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +yamlNode.prototype.setProperty = function (property) { + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); +} + +module.exports = yamlNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js new file mode 100644 index 000000000..8fc32ab1e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js @@ -0,0 +1,47 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function splitNode(id) { + nodePage.call(this, id); +} + +util.inherits(splitNode, nodePage); + +splitNode.prototype.setSplitUsing = function (splt, spltt) { + var spltType = { + "str": 1, + "bin": 2, + "len": 3 + }; + + browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + spltType[spltt] + ']'); + browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', splt); +}; + +module.exports.splitNode = splitNode; + +function joinNode(id) { + nodePage.call(this, id); +} + +util.inherits(joinNode, nodePage); + +module.exports.joinNode = joinNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js new file mode 100644 index 000000000..0d7b13028 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js @@ -0,0 +1,39 @@ +/** + * 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 util = require('util'); + +var nodePage = require('../../node_page'); + +function batchNode(id) { + nodePage.call(this, id); +} + +batchNode.prototype.setMode = function (mode) { + browser.selectWithWait('#node-input-mode', mode); +} + +batchNode.prototype.setCount = function (count) { + browser.setValue('#node-input-count', count); +} + +batchNode.prototype.setOverlap = function (overlap) { + browser.setValue('#node-input-overlap', overlap); +} + +util.inherits(batchNode, nodePage); + +module.exports = batchNode; diff --git a/test/editor/pageobjects/nodes/node_page.js b/test/editor/pageobjects/nodes/node_page.js index 5250250e7..03e734cab 100644 --- a/test/editor/pageobjects/nodes/node_page.js +++ b/test/editor/pageobjects/nodes/node_page.js @@ -35,10 +35,11 @@ Node.prototype.clickOk = function () { browser.pause(50); } -Node.prototype.connect = function (targetNode) { - var outputPort = this.id + '/*[@class="red-ui-flow-port-output"]'; +Node.prototype.connect = function (targetNode, port) { + port = port || 1; + var outputPort = this.id + '/*[@class="red-ui-flow-port-output"][' + port + ']'; var inputPort = targetNode.id + '/*[@class="red-ui-flow-port-input"]'; - browser.dragAndDrop(outputPort, inputPort) + browser.dragAndDrop(outputPort, inputPort); } Node.prototype.clickLeftButton = function () { diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 744c1570d..008ecc625 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -16,37 +16,70 @@ var injectNode = require('./core/common/20-inject_page'); var debugNode = require('./core/common/21-debug_page'); +var completeNode = require('./core/common/24-complete_page'); +var catchNode = require('./core/common/25-catch_page'); +var statusNode = require('./core/common/25-status_page'); +var commentNode = require('./core/common/90-comment_page'); var functionNode = require('./core/function/10-function_page'); +var switchNode = require('./core/function/10-switch_page'); var changeNode = require('./core/function/15-change_page'); var rangeNode = require('./core/function/16-range_page'); var templateNode = require('./core/function/80-template_page'); -var mqttInNode = require('./core/network/10-mqttin_page'); -var mqttOutNode = require('./core/network/10-mqttout_page'); +var delayNode = require('./core/function/89-delay_page'); +var triggerNode = require('./core/function/89-trigger_page'); +var execNode = require('./core/function/90-exec_page'); +var mqttInNode = require('./core/network/10-mqtt_page').mqttInNode; +var mqttOutNode = require('./core/network/10-mqtt_page').mqttOutNode; var httpInNode = require('./core/network/21-httpin_page'); var httpResponseNode = require('./core/network/21-httpresponse_page'); var httpRequestNode = require('./core/network/21-httprequest_page'); +var websocketInNode = require('./core/network/22-websocket_page').websocketInNode; +var websocketOutNode = require('./core/network/22-websocket_page').websocketOutNode; +var splitNode = require('./core/sequence/17-split_page').splitNode; +var joinNode = require('./core/sequence/17-split_page').joinNode; +var batchNode = require('./core/sequence/19-batch_page'); +var csvNode = require('./core/parsers/70-CSV_page'); var htmlNode = require('./core/parsers/70-HTML_page'); var jsonNode = require('./core/parsers/70-JSON_page'); +var xmlNode = require('./core/parsers/70-XML_page'); +var yamlNode = require('./core/parsers/70-YAML_page'); var fileInNode = require('./core/storage/10-filein_page'); var nodeCatalog = { // common "inject": injectNode, "debug": debugNode, + "complete": completeNode, + "catch": catchNode, + "status": statusNode, + "comment": commentNode, // function "function": functionNode, + "switch": switchNode, "change": changeNode, "range": rangeNode, "template": templateNode, + "delay": delayNode, + "trigger": triggerNode, + "exec": execNode, // network "mqttIn": mqttInNode, "mqttOut": mqttOutNode, "httpIn": httpInNode, "httpResponse": httpResponseNode, "httpRequest": httpRequestNode, + "websocketIn": websocketInNode, + "websocketOut": websocketOutNode, + // sequence + "split": splitNode, + "join": joinNode, + "batch": batchNode, // parser + "csv": csvNode, "html": htmlNode, "json": jsonNode, + "xml": xmlNode, + "yaml": yamlNode, // storage "fileIn": fileInNode }; diff --git a/test/editor/pageobjects/util/util_page.js b/test/editor/pageobjects/util/util_page.js index 02508c831..3a764eb93 100644 --- a/test/editor/pageobjects/util/util_page.js +++ b/test/editor/pageobjects/util/util_page.js @@ -70,7 +70,7 @@ function init() { var ret = repeatUntilSuccess(function(args) { return browser.selectByValue(args[0], args[1]); - }, [selector, value]); + }, [selector, value.toString()]); return ret; } catch (e) { console.trace(); diff --git a/test/editor/specs/scenario/cookbook_dataformats_uispec.js b/test/editor/specs/scenario/cookbook_dataformats_uispec.js new file mode 100644 index 000000000..23bb4fb1b --- /dev/null +++ b/test/editor/specs/scenario/cookbook_dataformats_uispec.js @@ -0,0 +1,364 @@ +/** + * 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 when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('working with data formats', function () { + it('convert to/from JSON', function () { + var injectNode1 = workspace.addNode('inject'); + var jsonNode1 = workspace.addNode('json'); + var debugNode1 = workspace.addNode('debug'); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + jsonNode1.edit(); + jsonNode1.setProperty('payload'); + jsonNode1.clickOk(); + + injectNode1.connect(jsonNode1); + jsonNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var jsonNode2 = workspace.addNode('json'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{"a":1}'); + injectNode2.clickOk(); + + jsonNode2.edit(); + jsonNode2.setProperty('payload'); + jsonNode2.clickOk(); + + injectNode2.connect(jsonNode2); + jsonNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql('1'); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"{"a":1}"'); + }); + + it('convert to/from XML', function () { + var injectNode1 = workspace.addNode('inject', 0); + var templateNode1 = workspace.addNode('template', 200); + var xmlNode1 = workspace.addNode('xml', 400); + var debugNode1 = workspace.addNode('debug', 600); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + templateNode1.edit(); + templateNode1.setFormat('text'); + templateNode1.setSyntax('plain'); + templateNode1.setTemplate('' + + ' Nick' + + ' Dave' + + ' Reminder' + + ' Update the website' + + ''); + templateNode1.clickOk(); + + xmlNode1.edit(); + xmlNode1.setProperty('payload'); + xmlNode1.clickOk(); + + injectNode1.connect(templateNode1); + templateNode1.connect(xmlNode1); + xmlNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var xmlNode2 = workspace.addNode('xml'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{' + + ' "note": {' + + ' "$": { "priority": "high" },' + + ' "to": [ "Nick" ],' + + ' "from": [ "Dave" ],' + + ' "heading": [ "Reminder" ],' + + ' "body": [ "Update the website" ]' + + ' }' + + '}'); + injectNode2.clickOk(); + + xmlNode2.edit(); + xmlNode2.setProperty('payload'); + xmlNode2.clickOk(); + + injectNode2.connect(xmlNode2); + xmlNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql('object'); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"' + + '' + + 'Nick' + + 'Dave' + + 'Reminder' + + 'Update the website' + + '"'); + }); + + it('convert to/from YAML', function () { + var injectNode1 = workspace.addNode('inject', 0); + var templateNode1 = workspace.addNode('template', 200); + var yamlNode1 = workspace.addNode('yaml', 400); + var debugNode1 = workspace.addNode('debug', 600); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + templateNode1.edit(); + templateNode1.setFormat('yaml'); + templateNode1.setSyntax('plain'); + templateNode1.setTemplate('a: 1\n' + + 'b:\n' + + ' - 1\n' + + '- 2\n' + + '- 3'); + templateNode1.clickOk(); + + yamlNode1.edit(); + yamlNode1.setProperty('payload'); + yamlNode1.clickOk(); + + injectNode1.connect(templateNode1); + templateNode1.connect(yamlNode1); + yamlNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var yamlNode2 = workspace.addNode('yaml'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{"a":1, "b":[1,2,3]}'); + injectNode2.clickOk(); + + yamlNode2.edit(); + yamlNode2.setProperty('payload'); + yamlNode2.clickOk(); + + injectNode2.connect(yamlNode2); + yamlNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql([ '1', 'array[3]' ]); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"a: 1↵b:↵ - 1↵ - 2↵ - 3↵"'); + }); + + it('generate CSV output', function () { + var injectNode1 = workspace.addNode('inject', 0); + var changeNode1 = workspace.addNode('change', 200); + var csvNode1 = workspace.addNode('csv', 400); + var debugNode1 = workspace.addNode('debug', 600); + + changeNode1.edit(); + changeNode1.ruleSet('payload', 'msg', '{' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + '}', 'jsonata'); + changeNode1.clickOk(); + + csvNode1.edit(); + csvNode1.setColumns('a,b,c'); + csvNode1.clickOk(); + + injectNode1.connect(changeNode1); + changeNode1.connect(csvNode1); + csvNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject', 0, 80); + var changeNode2 = workspace.addNode('change', 200, 80); + var csvNode2 = workspace.addNode('csv', 400, 80); + var debugNode2 = workspace.addNode('debug', 600, 80); + + changeNode2.edit(); + changeNode2.ruleSet('payload', 'msg', '[' + + ' {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }' + + ']', 'jsonata'); + changeNode2.clickOk(); + + csvNode2.edit(); + csvNode2.setColumns('a,b,c'); + csvNode2.setIncludeRow(true); + csvNode2.clickOk(); + + injectNode2.connect(changeNode2); + changeNode2.connect(csvNode2); + csvNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.match(/^"([1-9]?[0-9],){2}[1-9]?[0-9]↵"$/); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.match(/^"a,b,c↵(([1-9]?[0-9],){2}[1-9]?[0-9]↵){4}"$/); + }); + + it('parse CSV input', function () { + var injectNode = workspace.addNode('inject'); + var templateNode = workspace.addNode('template'); + var csvNode = workspace.addNode('csv'); + var debugNode = workspace.addNode('debug'); + + templateNode.edit(); + templateNode.setFormat('handlebars'); + templateNode.setSyntax('mustache'); + templateNode.setTemplate('# This is some random data\n' + + 'a,b,c\n' + + '80,18,2\n' + + '52,36,10\n' + + '91,18,61\n' + + '32,47,65'); + templateNode.clickOk(); + + csvNode.edit(); + csvNode.setSkipLines(1); + csvNode.setFirstRow4Names(true); + csvNode.setOutput('mult'); + csvNode.clickOk(); + + injectNode.connect(templateNode); + templateNode.connect(csvNode); + csvNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql([ 'object', 'object', 'object', 'object' ]); + }); + + it('simple GET request', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var htmlNode = workspace.addNode('html'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl('https://nodered.org'); + httpRequestNode.clickOk(); + + htmlNode.edit(); + htmlNode.setSelector('.node-red-latest-version'); + htmlNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(htmlNode); + htmlNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.match(/^"v[0-9]+\.[0-9]+\.[0-9]"$/); + }); + + it('split text into one message per line', function () { + var injectNode = workspace.addNode('inject'); + var templateNode = workspace.addNode('template'); + var splitNode = workspace.addNode('split'); + var changeNode = workspace.addNode('change'); + var joinNode = workspace.addNode('join'); + var debugNode = workspace.addNode('debug'); + + templateNode.edit(); + templateNode.setFormat('handlebars'); + templateNode.setSyntax('mustache'); + templateNode.setTemplate('one\ntwo\nthree\nfour\nfive'); + templateNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('payload', 'msg', '(parts.index+1) & ": " & payload', 'jsonata'); + changeNode.clickOk(); + + injectNode.connect(templateNode); + templateNode.connect(splitNode); + splitNode.connect(changeNode); + changeNode.connect(joinNode); + joinNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"1: one↵2: two↵3: three↵4: four↵5: five"'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_errorhandling_uispec.js b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js new file mode 100644 index 000000000..5285c5c0f --- /dev/null +++ b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js @@ -0,0 +1,74 @@ +/** + * 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 when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('messages', function () { + it('trigger a flow when a node throws an error', function () { + var injectNode = workspace.addNode('inject'); + var functionNode = workspace.addNode('function'); + var catchNode = workspace.addNode('catch', 0 , 80); + var debugNode = workspace.addNode('debug'); + + functionNode.edit(); + functionNode.setFunction('node.error("an example error", msg);'); + functionNode.clickOk(); + + catchNode.edit(); + catchNode.setScope(functionNode); + catchNode.clickOk(); + + debugNode.edit(); + debugNode.setOutput('error'); + debugNode.clickOk(); + + injectNode.connect(functionNode); + catchNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql(['"an example error"', 'function']); + }); + + // skip this case since the flow outputs random results. + it.skip('automatically retry an action after an error'); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js new file mode 100644 index 000000000..724d1c56f --- /dev/null +++ b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js @@ -0,0 +1,81 @@ +/** + * 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 when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('flow control', function () { + it('trigger a flow whenever Node-RED starts', function () { + var injectNode = workspace.addNode('inject'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'Started!'); + injectNode.setOnce(true); + injectNode.clickOk(); + injectNode.connect(debugNode); + + debugTab.open(); + workspace.deploy(); + debugTab.getMessage().should.eql('"Started!"'); + }); + + it('trigger a flow at regular intervals', function () { + var injectNode = workspace.addNode('inject'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setRepeat('interval'); + injectNode.setRepeatInterval(1); + injectNode.clickOk(); + injectNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + specUtil.pause(1000); + var t1 = Number(debugTab.getMessage(1)); + t1.should.within(1500000000000, 3000000000000); + specUtil.pause(1000); + debugTab.getMessage(2).should.within(t1 + 900, 3000000000000); + }); + + // skip this case since it needs up to one minite. + it.skip('trigger a flow at a specific time'); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_endpoint_uispec.js b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js similarity index 98% rename from test/editor/specs/scenario/cookbook_endpoint_uispec.js rename to test/editor/specs/scenario/cookbook_httpendpoints_uispec.js index 9a2ed0710..134498370 100644 --- a/test/editor/specs/scenario/cookbook_endpoint_uispec.js +++ b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js @@ -23,16 +23,16 @@ var workspace = require('../../pageobjects/editor/workspace_page'); var httpNodeRoot = "/api"; // https://cookbook.nodered.org/ -describe('cookbook', function() { - beforeEach(function() { +describe('cookbook', function () { + beforeEach(function () { workspace.init(); }); - before(function() { + before(function () { helper.startServer(); }); - after(function() { + after(function () { helper.stopServer(); }); @@ -359,7 +359,7 @@ describe('cookbook', function() { debugTab.getMessage().indexOf('Text file').should.not.eql(-1); }); - it('post raw data to a flow', function() { + it('post raw data to a flow', function () { var httpInNode = workspace.addNode("httpIn"); var templateNode = workspace.addNode("template"); var httpResponseNode = workspace.addNode("httpResponse"); @@ -383,7 +383,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("str", "Nick"); injectNode.clickOk(); @@ -427,7 +427,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("str", "name=Nick"); injectNode.clickOk(); @@ -451,7 +451,7 @@ describe('cookbook', function() { debugTab.getMessage().indexOf('Hello Nick!').should.not.eql(-1); }); - it('post JSON data to a flow', function() { + it('post JSON data to a flow', function () { var httpInNode = workspace.addNode("httpIn"); var templateNode = workspace.addNode("template"); var httpResponseNode = workspace.addNode("httpResponse"); @@ -476,7 +476,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("json", '{"name":"Nick"}'); injectNode.clickOk(); diff --git a/test/editor/specs/scenario/cookbook_httprequests_uispec.js b/test/editor/specs/scenario/cookbook_httprequests_uispec.js new file mode 100644 index 000000000..797b06041 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_httprequests_uispec.js @@ -0,0 +1,300 @@ +/** + * 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 when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('HTTP requests', function () { + it('simple get request', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var htmlNode = workspace.addNode('html'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl(helper.url()); + httpRequestNode.clickOk(); + + htmlNode.edit(); + htmlNode.setSelector('title'); + htmlNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(htmlNode); + htmlNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Node-RED"'); + }); + + it('set the URL of a request', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', helper.url()); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('url', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.containEql('Node-RED'); + }); + + it('set the URL of a request using a template', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'settings'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('query', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setUrl(helper.url() + '/{{{query}}}'); + httpRequestNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.containEql('httpNodeRoot'); + }); + + it('set the query string parameters', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'Nick'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('query', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}'); + httpRequestNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('get'); + httpInNode.setUrl('/set-query'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('Hello {{req.query.q}}'); + templateNode.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello Nick"'); + }); + + it('get a parsed JSON response', function () { + var injectNode = workspace.addNode('inject'); + var changeNodeSetPost = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'json-response'); + injectNode.clickOk(); + + changeNodeSetPost.edit(); + changeNodeSetPost.ruleSet('post', 'msg', 'payload', 'msg'); + changeNodeSetPost.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + var url = helper.url() + httpNodeRoot + '/{{post}}'; + httpRequestNode.setUrl(url); + httpRequestNode.setReturn('obj'); + httpRequestNode.clickOk(); + + debugNode.edit(); + debugNode.setOutput('payload.title'); + debugNode.clickOk(); + + injectNode.connect(changeNodeSetPost); + changeNodeSetPost.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var changeNodeSetHeader = workspace.addNode('change'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('get'); + httpInNode.setUrl('/json-response'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('{"title": "Hello"}'); + templateNode.clickOk(); + + changeNodeSetHeader.edit(); + changeNodeSetHeader.ruleSet('headers', 'msg', '{"content-type":"application/json"}', 'json'); + changeNodeSetHeader.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(changeNodeSetHeader); + changeNodeSetHeader.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello"'); + }); + + it('get a binary response', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl(helper.url() + '/settings'); + httpRequestNode.setReturn('bin'); + httpRequestNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + + debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']); + }); + + it('set a request header', function () { + var injectNode = workspace.addNode('inject'); + var functionNode = workspace.addNode('function'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + functionNode.edit(); + functionNode.setFunction('msg.payload = "data to post";\nreturn msg;'); + functionNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setMethod('POST'); + var url = helper.url() + httpNodeRoot + '/set-header'; + httpRequestNode.setUrl(url); + httpRequestNode.clickOk(); + + injectNode.connect(functionNode); + functionNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('post'); + httpInNode.setUrl('/set-header'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('{{ payload }}'); + templateNode.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"data to post"'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_messages_uispec.js b/test/editor/specs/scenario/cookbook_messages_uispec.js new file mode 100644 index 000000000..78facbcda --- /dev/null +++ b/test/editor/specs/scenario/cookbook_messages_uispec.js @@ -0,0 +1,142 @@ +/** + * 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 when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('messages', function () { + it('set a message property to a fixed value', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + changeNode.edit(); + changeNode.ruleSet('payload', 'msg', 'Hello World!'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello World!"'); + }); + + it('delete a message property', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + changeNode.edit(); + changeNode.ruleDelete(); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('undefined'); + }); + + it('move a message property', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setTopic('Hello'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleMove('topic', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello"'); + }); + + it('map a property between different numeric ranges', function () { + var injectNode1 = workspace.addNode('inject'); + var injectNode2 = workspace.addNode('inject', 0, 100); + var injectNode3 = workspace.addNode('inject', 0, 200); + var rangeNode = workspace.addNode('range', 200, 100); + var debugNode = workspace.addNode('debug', 400); + + injectNode1.edit(); + injectNode1.setPayload('num', 0); + injectNode1.clickOk(); + injectNode2.edit(); + injectNode2.setPayload('num', 512); + injectNode2.clickOk(); + injectNode3.edit(); + injectNode3.setPayload('num', 1023); + injectNode3.clickOk(); + + rangeNode.edit(); + rangeNode.setAction('clamp'); + rangeNode.setRange(0, 1023, 0, 5); + rangeNode.clickOk(); + + injectNode1.connect(rangeNode); + injectNode2.connect(rangeNode); + injectNode3.connect(rangeNode); + rangeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage(1).should.eql('0'); + injectNode2.clickLeftButton(); + debugTab.getMessage(2).should.eql('2.5024437927663734'); + injectNode3.clickLeftButton(); + debugTab.getMessage(3).should.eql('5'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_mqtt_uispec.js b/test/editor/specs/scenario/cookbook_mqtt_uispec.js index 655074750..b68170eb5 100644 --- a/test/editor/specs/scenario/cookbook_mqtt_uispec.js +++ b/test/editor/specs/scenario/cookbook_mqtt_uispec.js @@ -22,7 +22,6 @@ var helper = require("../../editor_helper"); var debugTab = require('../../pageobjects/editor/debugTab_page'); var workspace = require('../../pageobjects/editor/workspace_page'); var specUtil = require('../../pageobjects/util/spec_util_page'); -var mqttConfig = require('../../pageobjects/nodes/core/network/10-mqttconfig_page.js'); var httpNodeRoot = "/api"; @@ -73,9 +72,9 @@ describe('cookbook', function () { var mqttOutNode = workspace.addNode("mqttOut"); mqttOutNode.edit(); - mqttConfig.edit(); - mqttConfig.setServer("localhost", moscaSettings.port); - mqttConfig.clickOk(); + mqttOutNode.mqttBrokerNode.edit(); + mqttOutNode.mqttBrokerNode.setServer("localhost", moscaSettings.port); + mqttOutNode.mqttBrokerNode.clickOk(); mqttOutNode.clickOk(); workspace.deploy(); diff --git a/test/editor/specs/scenario/cookbook_uispec.js b/test/editor/specs/scenario/cookbook_uispec.js deleted file mode 100644 index 6e16ca8a7..000000000 --- a/test/editor/specs/scenario/cookbook_uispec.js +++ /dev/null @@ -1,441 +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 when = require('when'); -var should = require("should"); -var fs = require('fs-extra'); - -var helper = require("../../editor_helper"); -var debugTab = require('../../pageobjects/editor/debugTab_page'); -var workspace = require('../../pageobjects/editor/workspace_page'); -var specUtil = require('../../pageobjects/util/spec_util_page'); - -var httpNodeRoot = "/api"; - -// https://cookbook.nodered.org/ -describe('cookbook', function() { - beforeEach(function() { - workspace.init(); - }); - - before(function() { - helper.startServer(); - }); - - after(function() { - helper.stopServer(); - }); - - describe('messages', function() { - it('set a message property to a fixed value', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - changeNode.edit(); - changeNode.ruleSet("payload", "msg", "Hello World!"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello World!"'); - }); - - it('delete a message property', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - changeNode.edit(); - changeNode.ruleDelete(); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql("undefined"); - }); - - it('move a message property', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setTopic("Hello"); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleMove("topic", "payload"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello"'); - }); - - it('map a property between different numeric ranges', function() { - var injectNode1 = workspace.addNode("inject"); - var injectNode2 = workspace.addNode("inject", 0, 100); - var injectNode3 = workspace.addNode("inject", 0, 200); - var rangeNode = workspace.addNode("range", 200, 100); - var debugNode = workspace.addNode("debug", 400); - - injectNode1.edit(); - injectNode1.setPayload("num", 0); - injectNode1.clickOk(); - injectNode2.edit(); - injectNode2.setPayload("num", 512); - injectNode2.clickOk(); - injectNode3.edit(); - injectNode3.setPayload("num", 1023); - injectNode3.clickOk(); - - rangeNode.edit(); - rangeNode.setAction("clamp"); - rangeNode.setRange(0, 1023, 0, 5); - rangeNode.clickOk(); - - injectNode1.connect(rangeNode); - injectNode2.connect(rangeNode); - injectNode3.connect(rangeNode); - rangeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode1.clickLeftButton(); - debugTab.getMessage(1).should.eql('0'); - injectNode2.clickLeftButton(); - debugTab.getMessage(2).should.eql('2.5024437927663734'); - injectNode3.clickLeftButton(); - debugTab.getMessage(3).should.eql('5'); - }); - }); - - describe('flow control', function() { - it('trigger a flow whenever Node-RED starts', function() { - var injectNode = workspace.addNode("inject"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", "Started!") - injectNode.setOnce(true); - injectNode.clickOk(); - injectNode.connect(debugNode); - - debugTab.open(); - workspace.deploy(); - debugTab.getMessage().should.eql('"Started!"'); - }); - - it('trigger a flow at regular intervals', function() { - var injectNode = workspace.addNode("inject"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setRepeat("interval"); - injectNode.setRepeatInterval(1); - injectNode.clickOk(); - injectNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - specUtil.pause(1000); - var t1 = Number(debugTab.getMessage(1)); - t1.should.within(1500000000000, 3000000000000); - specUtil.pause(1000); - debugTab.getMessage(2).should.within(t1 + 900, 3000000000000); - }); - - // skip this case since it needs up to one minite. - it.skip('trigger a flow at a specific time'); - }); - - describe('HTTP requests', function() { - it('simple get request', function() { - var injectNode = workspace.addNode("inject"); - var httpRequetNode = workspace.addNode("httpRequest"); - var htmlNode = workspace.addNode("html"); - var debugNode = workspace.addNode("debug"); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - httpRequetNode.setUrl(helper.url()); - httpRequetNode.clickOk(); - - htmlNode.edit(); - htmlNode.setSelector("title"); - htmlNode.clickOk(); - - injectNode.connect(httpRequetNode); - httpRequetNode.connect(htmlNode); - htmlNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Node-RED"'); - }); - - it('set the URL of a request', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", helper.url()); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("url", "msg", "payload", "msg"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.containEql('Node-RED'); - }); - - it('set the URL of a request using a template', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", 'settings'); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("query", "msg", "payload", "msg"); - changeNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setUrl(helper.url() + "/{{{query}}}"); - httpRequetNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.containEql('httpNodeRoot'); - }); - - it('set the query string parameters', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", 'Nick'); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("query", "msg", "payload", "msg"); - changeNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}'); - httpRequetNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("get"); - httpInNode.setUrl("/set-query"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate("Hello {{req.query.q}}"); - templateNode.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello Nick"'); - }); - - it('get a parsed JSON response', function() { - var injectNode = workspace.addNode("inject"); - var changeNodeSetPost = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", "json-response"); - injectNode.clickOk(); - - changeNodeSetPost.edit(); - changeNodeSetPost.ruleSet("post", "msg", "payload", "msg"); - changeNodeSetPost.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - var url = helper.url() + httpNodeRoot + "/{{post}}"; - httpRequetNode.setUrl(url); - httpRequetNode.setReturn("obj"); - httpRequetNode.clickOk(); - - debugNode.edit(); - debugNode.setOutput(".title"); - debugNode.clickOk(); - - injectNode.connect(changeNodeSetPost); - changeNodeSetPost.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var changeNodeSetHeader = workspace.addNode("change"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("get"); - httpInNode.setUrl("/json-response"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate('{"title": "Hello"}'); - templateNode.clickOk(); - - changeNodeSetHeader.edit(); - changeNodeSetHeader.ruleSet("headers", "msg", '{"content-type":"application/json"}', "json"); - changeNodeSetHeader.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(changeNodeSetHeader); - changeNodeSetHeader.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello"'); - }); - - it('get a binary response', function() { - var injectNode = workspace.addNode("inject"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - httpRequetNode.setUrl(helper.url() + "/settings"); - httpRequetNode.setReturn("bin"); - httpRequetNode.clickOk(); - - injectNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - - debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']); - }); - - it('set a request header', function() { - var injectNode = workspace.addNode("inject"); - var functionNode = workspace.addNode("function"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - functionNode.edit(); - functionNode.setFunction('msg.payload = "data to post";\nreturn msg;'); - functionNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setMethod("POST"); - var url = helper.url() + httpNodeRoot + "/set-header"; - httpRequetNode.setUrl(url); - httpRequetNode.clickOk(); - - injectNode.connect(functionNode); - functionNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("post"); - httpInNode.setUrl("/set-header"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate("{{ payload }}"); - templateNode.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"data to post"'); - }); - }); -}); diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index be0094ccf..7d7450580 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -759,6 +759,41 @@ describe('trigger node', function() { }); }); + it('should be able output the 2nd payload and handle multiple topics', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"false", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"80", bytopic:"topic", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + if (c === 0) { + msg.should.have.a.property("payload", "Goodbye1"); + msg.should.have.a.property("topic", "test1"); + c += 1; + } + else { + msg.should.have.a.property("payload", "Goodbye2"); + msg.should.have.a.property("topic", "test2"); + done(); + } + } + catch(err) { done(err); } + }); + n1.emit("input", {payload:"Hello1", topic:"test1"}); + setTimeout( function() { + n1.emit("input", {payload:"Hello2", topic:"test2"}); + },20); + setTimeout( function() { + n1.emit("input", {payload:"Goodbye2", topic:"test2"}); + },20); + setTimeout( function() { + n1.emit("input", {payload:"Goodbye1", topic:"test1"}); + },20); + }); + }); + it('should be able to apply mustache templates to payloads', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:"50", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js index 3d38a9ac8..2c45f0e02 100644 --- a/test/nodes/core/sequence/17-split_spec.js +++ b/test/nodes/core/sequence/17-split_spec.js @@ -517,6 +517,49 @@ describe('JOIN node', function() { }); }); + it('should join things into an array after a count with a buffer join set', function(done) { + var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joinerType:"bin", joiner:"" ,mode:"custom"}, + {id:"n2", type:"helper"}]; + helper.load(joinNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("payload"); + msg.payload.should.be.an.Array(); + msg.payload[0].should.equal(1); + msg.payload[1].should.equal(true); + //msg.payload[2].a.should.equal(1); + done(); + } + catch(e) {done(e);} + }); + n1.receive({payload:1}); + n1.receive({payload:true}); + n1.receive({payload:{a:1}}); + }); + }); + + it('should join strings into a buffer after a count', function(done) { + var flow = [{id:"n1", type:"join", wires:[["n2"]], count:2, build:"buffer", joinerType:"bin", joiner:"", mode:"custom"}, + {id:"n2", type:"helper"}]; + helper.load(joinNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("payload"); + msg.payload.length.should.equal(10); + msg.payload.toString().should.equal("helloworld"); + done(); + } + catch(e) {done(e);} + }); + n1.receive({payload:"hello"}); + n1.receive({payload:"world"}); + }); + }); + it('should join things into an object after a count', function(done) { var flow = [{id:"n1", type:"join", wires:[["n2"]], count:5, build:"object", mode:"custom"}, {id:"n2", type:"helper"}]; diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js index a7c85efa2..dafbbc69b 100644 --- a/test/unit/@node-red/runtime/lib/api/flows_spec.js +++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js @@ -53,7 +53,7 @@ describe("runtime-api/flows", function() { var loadFlows; var reloadError = false; beforeEach(function() { - setFlows = sinon.spy(function(flows,type) { + setFlows = sinon.spy(function(flows,credentials,type) { if (flows[0] === "error") { var err = new Error("error"); err.code = "error"; @@ -91,7 +91,19 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("full"); + setFlows.lastCall.args[2].should.eql("full"); + done(); + }).catch(done); + }); + it("includes credentials when part of the request", function(done) { + flows.setFlows({ + flows: {flows:[4,5,6], credentials: {$:"creds"}}, + }).then(function(result) { + result.should.eql({rev:"newRev"}); + setFlows.called.should.be.true(); + setFlows.lastCall.args[0].should.eql([4,5,6]); + setFlows.lastCall.args[1].should.eql({$:"creds"}); + setFlows.lastCall.args[2].should.eql("full"); done(); }).catch(done); }); @@ -103,7 +115,7 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("nodes"); + setFlows.lastCall.args[2].should.eql("nodes"); done(); }).catch(done); }); @@ -125,7 +137,7 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("nodes"); + setFlows.lastCall.args[2].should.eql("nodes"); done(); }).catch(done); }); diff --git a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js index 3c9030b03..6fd76a421 100644 --- a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js @@ -32,17 +32,20 @@ describe('context', function() { return Context.close(); }); it('stores local property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.get("foo")); context1.set("foo","test"); context1.get("foo").should.equal("test"); }); it('stores local property - creates parent properties',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); context1.set("foo.bar","test"); context1.get("foo").should.eql({bar:"test"}); }); it('deletes local property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); context1.set("foo.abc.bar1","test1"); context1.set("foo.abc.bar2","test2"); @@ -55,12 +58,14 @@ describe('context', function() { should.not.exist(context1.get("foo")); }); it('stores flow property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.flow.get("foo")); context1.flow.set("foo","test"); context1.flow.get("foo").should.equal("test"); }); it('stores global property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.global.get("foo")); context1.global.set("foo","test"); @@ -68,6 +73,7 @@ describe('context', function() { }); it('keeps local context local', function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowA"); @@ -79,6 +85,7 @@ describe('context', function() { should.not.exist(context2.get("foo")); }); it('flow context accessible to all flow nodes', function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowA"); @@ -91,6 +98,8 @@ describe('context', function() { }); it('flow context not shared to nodes on other flows', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowB"); @@ -103,6 +112,9 @@ describe('context', function() { }); it('global context shared to all nodes', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB") + var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowB"); @@ -115,6 +127,7 @@ describe('context', function() { }); it('context.flow/global are not enumerable', function() { + var flowContextA = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); Object.keys(context1).length.should.equal(0); Object.keys(context1.flow).length.should.equal(0); @@ -122,6 +135,7 @@ describe('context', function() { }) it('context.flow/global cannot be deleted', function() { + var flowContextA = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); delete context1.flow; should.exist(context1.flow); @@ -130,6 +144,7 @@ describe('context', function() { }) it('deletes context',function() { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); should.not.exist(context.get("foo")); context.set("foo","abc"); @@ -142,6 +157,7 @@ describe('context', function() { }); it('enumerates context keys - sync', function() { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var keys = context.keys(); @@ -160,6 +176,7 @@ describe('context', function() { }); it('enumerates context keys - async', function(done) { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var keys = context.keys(function(err,keys) { @@ -183,6 +200,7 @@ describe('context', function() { it('should enumerate only context keys when GlobalContext was given - sync', function() { Context.init({functionGlobalContext: {foo:"bar"}}); Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); context.global.set("foo2","bar2"); var keys = context.global.keys(); @@ -195,6 +213,7 @@ describe('context', function() { it('should enumerate only context keys when GlobalContext was given - async', function(done) { Context.init({functionGlobalContext: {foo:"bar"}}); Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); context.global.set("foo2","bar2"); context.global.keys(function(err,keys) { @@ -210,6 +229,7 @@ describe('context', function() { it('returns functionGlobalContext value if store value undefined', function() { Context.init({functionGlobalContext: {foo:"bar"}}); return Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var v = context.global.get('foo'); v.should.equal('bar'); @@ -219,6 +239,7 @@ describe('context', function() { it('returns functionGlobalContext sub-value if store value undefined', function() { Context.init({functionGlobalContext: {foo:{bar:123}}}); return Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var v = context.global.get('foo.bar'); should.equal(v,123); @@ -227,40 +248,67 @@ describe('context', function() { describe("$parent", function() { it('should get undefined for $parent without key', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); + var context1 = Context.get("1","flowB"); var parent = context1.get("$parent"); should.equal(parent, undefined); }); it('should get undefined for $parent of root', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - var parent = context1.get("$parent.$parent.K"); + var context1 = Context.get("1","flowB"); + var parent = context1.flow.get("$parent.$parent.K"); should.equal(parent, undefined); }); - it('should get value in $parent', function() { + it('should get undefined for $parent of root - callback', function(done) { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - context0.set("K", "v"); - var v = context1.get("$parent.K"); + var context1 = Context.get("1","flowB"); + context1.flow.get("$parent.$parent.K", function(err, result) { + try { + should.equal(err, undefined); + should.equal(result, undefined); + done(); + } catch(err) { + done(err); + } + }); + + }); + + it('should get value in $parent', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") + var context0 = Context.get("0","flowA"); + var context1 = Context.get("1","flowB"); + flowContextA.set("K", "v"); + var v = context1.flow.get("$parent.K"); should.equal(v, "v"); }); it('should set value in $parent', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - context1.set("$parent.K", "v"); - var v = context0.get("K"); + var context1 = Context.get("1","flowB"); + context1.flow.set("$parent.K", "v"); + var v = flowContextA.get("K"); should.equal(v, "v"); }); it('should not contain $parent in keys', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); + var context1 = Context.get("1","flowB"); var parent = context1.get("$parent"); - context0.set("K0", "v0"); + flowContextA.set("K0", "v0"); context1.set("K1", "v1"); var keys = context1.keys(); keys.should.have.length(1); @@ -366,6 +414,7 @@ describe('context', function() { it('should ignore reserved storage name `_`', function(done) { Context.init({contextStorage:{_:{module:testPlugin}}}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow") var context = Context.get("1","flow"); var cb = function(){} context.set("foo","bar","_",cb); @@ -452,6 +501,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); var cb = function(){done("An error occurred")} Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set("foo","bar","test",cb); context.get("foo","test",cb); @@ -465,6 +515,7 @@ describe('context', function() { it('should store flow property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.flow.set("foo","bar","test",cb); @@ -479,6 +530,7 @@ describe('context', function() { it('should store global property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.global.set("foo","bar","test",cb); @@ -493,6 +545,7 @@ describe('context', function() { it('should store data to the default context when non-existent context storage was specified', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","bar","nonexist",cb); @@ -510,6 +563,7 @@ describe('context', function() { it('should use the default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","bar","default",cb); @@ -527,6 +581,7 @@ describe('context', function() { it('should use the alias of default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","alias",cb); @@ -541,10 +596,11 @@ describe('context', function() { done(); }).catch(done); }); - + it('should allow the store name to be provide in the key', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("#:(test)::foo","bar"); @@ -561,6 +617,7 @@ describe('context', function() { it('should use default as the alias of other context', function(done) { Context.init({contextStorage:contextAlias}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","alias",cb); @@ -575,6 +632,7 @@ describe('context', function() { it('should not throw an error using undefined storage for local context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.get("local","nonexist",cb); @@ -584,6 +642,7 @@ describe('context', function() { it('should throw an error using undefined storage for flow context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.flow.get("flow","nonexist",cb); @@ -595,6 +654,7 @@ describe('context', function() { var fGC = { "foo": 456 }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function() { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); // Get foo - should be value from fGC var v = context.global.get("foo"); @@ -615,6 +675,7 @@ describe('context', function() { var fGC = { "foo": 456 }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function() { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); // Get foo - should be value from fGC context.global.get("foo", function(err, v) { @@ -647,6 +708,7 @@ describe('context', function() { it('should return multiple values if key is an array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set("foo1","bar1","memory"); context.set("foo2","bar2","memory"); @@ -667,6 +729,7 @@ describe('context', function() { var fGC = { "foo1": 456, "foo2": {"bar":789} }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.global.get(["foo1","foo2.bar","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err) { @@ -685,6 +748,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); stubGet.onFirstCall().callsArgWith(2, "error2", "bar1"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow") var context = Context.get("1","flow"); context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err === "error2") { @@ -702,6 +766,7 @@ describe('context', function() { stubGet.onSecondCall().callsArgWith(2, null, "bar2"); stubGet.onThirdCall().callsArgWith(2, "error3"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err === "error1") { @@ -716,6 +781,7 @@ describe('context', function() { it('should store multiple properties if key and value are arrays', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err) { @@ -739,6 +805,7 @@ describe('context', function() { it('should deletes multiple properties', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err) { @@ -777,6 +844,7 @@ describe('context', function() { it('should use null for missing values if the value array is shorter than the key array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2"], "memory", function(err){ if (err) { @@ -804,6 +872,7 @@ describe('context', function() { it('should use null for missing values if the value is not array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], "bar1", "memory", function(err){ if (err) { @@ -831,6 +900,7 @@ describe('context', function() { it('should ignore the extra values if the value array is longer than the key array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3","ignored"], "memory", function(err){ if (err) { @@ -859,6 +929,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); stubSet.onFirstCall().callsArgWith(3, "error2"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err === "error2") { @@ -873,6 +944,7 @@ describe('context', function() { it('should throw an error if callback of context.get is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.get("foo", "memory", "callback"); done("should throw an error."); @@ -884,6 +956,7 @@ describe('context', function() { it('should not throw an error if callback of context.get is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.get("foo", "memory"); done(); @@ -893,6 +966,7 @@ describe('context', function() { it('should throw an error if callback of context.set is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.set("foo", "bar", "memory", "callback"); done("should throw an error."); @@ -904,6 +978,7 @@ describe('context', function() { it('should not throw an error if callback of context.set is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.set("foo", "bar", "memory"); done(); @@ -913,6 +988,7 @@ describe('context', function() { it('should throw an error if callback of context.keys is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.keys("memory", "callback"); done("should throw an error."); @@ -924,6 +1000,7 @@ describe('context', function() { it('should not throw an error if callback of context.keys is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.keys("memory"); done(); @@ -953,7 +1030,6 @@ describe('context', function() { }).catch(done); }); }); - describe('delete context',function(){ it('should not call delete() when external context storage is used', function(done) { Context.init({contextStorage:contextDefaultStorage}); diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js index a37d88f78..8865c5d3f 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js @@ -67,7 +67,10 @@ describe('flows/index', function() { }); return when.resolve(); }); - credentialsLoad = sinon.stub(credentials,"load",function() { + credentialsLoad = sinon.stub(credentials,"load",function(creds) { + if (creds && creds.hasOwnProperty("$") && creds['$'] === "fail") { + return when.reject("creds error"); + } return when.resolve(); }); flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) { @@ -177,6 +180,23 @@ describe('flows/index', function() { }); }); + it('sets the full flow including credentials', function(done) { + var originalConfig = [ + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"t1",type:"tab"} + ]; + var credentials = {"t1-1":{"a":1}}; + + flows.init({log:mockLog, settings:{},storage:storage}); + flows.setFlows(originalConfig,credentials).then(function() { + credentialsClean.called.should.be.false(); + credentialsLoad.called.should.be.true(); + credentialsLoad.lastCall.args[0].should.eql(credentials); + flows.getFlows().flows.should.eql(originalConfig); + done(); + }); + }); + it('updates existing flows with partial deployment - nodes', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -235,6 +255,20 @@ describe('flows/index', function() { }); }); + it('returns error if it cannot decrypt credentials', function(done) { + var originalConfig = [ + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"t1",type:"tab"} + ]; + var credentials = {"$":"fail"}; + + flows.init({log:mockLog, settings:{},storage:storage}); + flows.setFlows(originalConfig,credentials).then(function() { + done("Unexpected success when credentials couldn't be decrypted") + }).catch(function(err) { + done(); + }); + }); }); describe('#load', function() { diff --git a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js index 70152ea1c..69b6e3da6 100644 --- a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js +++ b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js @@ -72,45 +72,52 @@ describe('storage/localfilesystem/library', function() { }); function createObjectLibrary(type) { - type = type ||"object"; - var objLib = path.join(userDir,"lib",type); + type = type || "object"; + var objLib = path.join(userDir, "lib", type); try { fs.mkdirSync(objLib); - } catch(err) { + } catch (err) { } - fs.mkdirSync(path.join(objLib,"A")); - fs.mkdirSync(path.join(objLib,"B")); - fs.mkdirSync(path.join(objLib,"B","C")); + fs.mkdirSync(path.join(objLib, "A")); + fs.mkdirSync(path.join(objLib, "B")); + fs.mkdirSync(path.join(objLib, "B", "C")); + fs.mkdirSync(path.join(objLib, "D")); if (type === "functions" || type === "object") { - fs.writeFileSync(path.join(objLib,"file1.js"),"// abc: def\n// not a metaline \n\n Hi",'utf8'); - fs.writeFileSync(path.join(objLib,"B","file2.js"),"// ghi: jkl\n// not a metaline \n\n Hi",'utf8'); + fs.writeFileSync(path.join(objLib, "file1.js"), "// abc: def\n// not a metaline \n\n Hi", 'utf8'); + fs.writeFileSync(path.join(objLib, "B", "file2.js"), "// ghi: jkl\n// not a metaline \n\n Hi", 'utf8'); + fs.writeFileSync(path.join(objLib, "D", "file3.js"), "// mno: 日本語テスト\n\nこんにちわ", 'utf8'); } if (type === "flows" || type === "object") { - fs.writeFileSync(path.join(objLib,"B","flow.json"),"Hi",'utf8'); + fs.writeFileSync(path.join(objLib, "B", "flow.json"), "Hi", 'utf8'); } } - it('should return a directory listing of library objects',function(done) { - localfilesystemLibrary.init({userDir:userDir}).then(function() { + it('should return a directory listing of library objects', function (done) { + localfilesystemLibrary.init({userDir: userDir}).then(function () { createObjectLibrary(); - localfilesystemLibrary.getLibraryEntry('object','').then(function(flows) { - flows.should.eql([ 'A', 'B', { abc: 'def', fn: 'file1.js' } ]); - localfilesystemLibrary.getLibraryEntry('object','B').then(function(flows) { - flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' } ]); - localfilesystemLibrary.getLibraryEntry('object','B/C').then(function(flows) { + localfilesystemLibrary.getLibraryEntry('object', '').then(function (flows) { + flows.should.eql([ 'A', 'B', 'D', { abc: 'def', fn: 'file1.js' }]); + localfilesystemLibrary.getLibraryEntry('object', 'B').then(function (flows) { + flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' }]); + localfilesystemLibrary.getLibraryEntry('object', 'B/C').then(function (flows) { flows.should.eql([]); - done(); - }).catch(function(err) { + localfilesystemLibrary.getLibraryEntry('object', 'D').then(function (flows) { + flows.should.eql([{ mno: '日本語テスト', fn: 'file3.js' }]); + done(); + }).catch(function (err) { + done(err); + }); + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); }); @@ -203,4 +210,35 @@ describe('storage/localfilesystem/library', function() { done(err); }); }); + + it('should return a newly saved library flow (multi-byte character)',function(done) { + localfilesystemLibrary.init({userDir:userDir}).then(function() { + createObjectLibrary("flows"); + localfilesystemLibrary.getLibraryEntry('flows','B').then(function(flows) { + flows.should.eql([ 'C', {fn:'flow.json'} ]); + var ft = path.join("B","D","file4"); + localfilesystemLibrary.saveLibraryEntry('flows',ft,{mno:'pqr'},"こんにちわこんにちわこんにちわ").then(function() { + setTimeout(function() { + localfilesystemLibrary.getLibraryEntry('flows',path.join("B","D")).then(function(flows) { + flows.should.eql([ { mno: 'pqr', fn: 'file4.json' } ]); + localfilesystemLibrary.getLibraryEntry('flows',ft+".json").then(function(body) { + body.should.eql("こんにちわこんにちわこんにちわ"); + done(); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }) + }, 50); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }); });