├── .config.users.json ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── flows.json ├── flows_cred.json ├── nodes └── README.md ├── package.json ├── public ├── css │ ├── simplegrid.css │ └── style.css ├── favicon.ico ├── images │ ├── ets-globe.png │ ├── node-red-title-flow.png │ ├── node-red.png │ ├── nr-image-1.png │ ├── save-button.png │ └── tab.png └── index.html ├── settings.js └── utils ├── file-explorer-flow.json └── save-all-changes-flow.json /.config.users.json: -------------------------------------------------------------------------------- 1 | { 2 | "alerox": { 3 | "editor": { 4 | "view": { 5 | "view-show-grid": true, 6 | "view-snap-grid": true, 7 | "view-grid-size": 20, 8 | "view-node-status": true, 9 | "view-node-show-label": true, 10 | "view-show-tips": true, 11 | "view-show-welcome-tours": true 12 | }, 13 | "tours": { 14 | "welcome": "latest" 15 | }, 16 | "sidebar": { 17 | "order": [ 18 | "info", 19 | "help", 20 | "debug", 21 | "dashboard", 22 | "config", 23 | "context" 24 | ] 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .DS_Store 3 | .bash_history 4 | .bash_logout 5 | .bashrc 6 | .cache 7 | .forever 8 | .node-gyp 9 | node_modules 10 | .npm 11 | .profile 12 | run-local.bat 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node node_modules/node-red/red.js --settings ./settings.js --userDir ./ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodered-heroku 2 | A wrapper for deploying [Node-RED](http://nodered.org) into the [Heroku](https://www.heroku.com). 3 | * DEMO: Flow Editor - [https://nodered-heroku.herokuapp.com/editor](https://nodered-heroku.herokuapp.com/editor) 4 | * DEMO: Dashboard UI - [https://nodered-heroku.herokuapp.com](https://nodered-heroku.herokuapp.com) 5 | 6 | 7 | ## Warning: Heroku doesn't automatically save flows, credentials and installed nodes 8 | ``` 9 | [TL,DR] Use the SAVE Inject node in the first flow (see step 5). 10 | ``` 11 | To overcome this, after having deployed the new flows, export All flows as *flows.json* file, and push it to the GitHub repo linked to Heroku. Do the same with *flows_cred.json* and *package.json* for credentials and nodes installed in Palette. Detail on step 5. 12 | 13 | ## 1. Deploying Node-RED to Heroku 14 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/hybuild-project/nodered-heroku) 15 | 16 | ## 2. Set up GitHub repo and Heroku app 17 | * Fork this GitHub repo. 18 | * Set GitHub as deploy source on Heroku setting. 19 | * Enable Automatic Deployment, so that every time any file is pushed to GitHub repo, Heroku will rebuild Node-RED with updated files. 20 | 21 | ## 3. Password protect the flow editor 22 | Set username and password for Node-RED Flow Editor: 23 | * **NODE_RED_USERNAME** - the username to secure the Flow Editor with 24 | * **NODE_RED_PASSWORD** - the password to secure the Flow Editor with 25 | 26 | ## 4. Access Node-Red on Cloud 27 | * Flow Editor - [nodered-on-cloud.herokuapp.com/editor](https://nodered-on-cloud.herokuapp.com/editor) 28 | * Dashboard UI - [nodered-on-cloud.herokuapp.com/ui](https://nodered-on-cloud.herokuapp.com/ui) 29 | * Home page - [nodered-on-cloud.herokuapp.com](https://nodered-on-cloud.herokuapp.com) 30 | 31 | ## 5. Export all flows, credentials and installed nodes 32 | ### Manual mode (original) 33 | * In Editor, to export *flows.json*, click hamburger icon `☰` (top right), click Export, choose tab "All flows", then Download. 34 | * To export all the other files, browse the /app folder, e.g., with this [flow](https://flows.nodered.org/flow/44bc7ad491aacb4253dd8a5f757b5407) or the [modified version](utils/file-explorer-flow.json), and download all files. 35 | * Push *flows.json*, *flows_cred.json*, *package.json* to GitHub, so that Node-RED is rebuilt with the latest files at Heroku restart. 36 | ### Alternative mode (recommended) 37 | * Use the `SAVE` Inject node in the [first flow](utils/save-all-changes-flow.json) to directly push all files to GitHub. 38 | 39 | ![SAVE](public/images/save-button.png) 40 | 41 | ## Some included nodes 42 | * Dashboard UI - node-red-dashboard 43 | * MQTT - node-red-contrib-aedes 44 | * Blynk Cloud - node-red-contrib-blynk-ws 45 | * Email - node-red-node-email 46 | * Telegram - node-red-contrib-telegrambot-home 47 | * InfluxDB, MongoDB, Modbus, OPC UA, Netatmo, PostgresSQL, Wordmap, etc. 48 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node-RED", 3 | "description": "A visual tool for wiring the Internet of Things, for Heroku deployment.", 4 | "keywords": [ 5 | "Node-RED", 6 | "IoT", 7 | "Internet of Things", 8 | "Node.js", 9 | "Node", 10 | "NodeRED", 11 | "Nodejs", 12 | "Heroku", 13 | "Cloud" 14 | ], 15 | "website": "http://nodered.org/", 16 | "repository": "https://github.com/hybuild-project/nodered-heroku.git", 17 | "logo": "http://nodered.org/node-red.png", 18 | "success_url": "/editor", 19 | "env": { 20 | "NODE_RED_USERNAME": { 21 | "description": "Set Username for Node-RED UI editor.", 22 | "value": "" 23 | }, 24 | "NODE_RED_PASSWORD": { 25 | "description": "Set Password for Node-RED UI editor.", 26 | "value": "" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /flows.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/flows.json -------------------------------------------------------------------------------- /flows_cred.json: -------------------------------------------------------------------------------- 1 | { 2 | "a8d2d040.73334": {}, 3 | "01b856875e0d074d": {}, 4 | "0c550dcd627c0c5f": { 5 | "password": "${NETATMO_BEARER}" 6 | }, 7 | "ee20ae5266a5e32c": { 8 | "token": "${BOT_TOKEN}" 9 | }, 10 | "9c09e087.a48e7": {}, 11 | "f08c8c22d21d93de": { 12 | "refreshToken": "${REFRESH_TOKEN}", 13 | "accessToken": "${ACCESS_TOKEN}", 14 | "email": "${EMAIL}", 15 | "cert": "${CERT}", 16 | "thingId": "${THING_ID}", 17 | "caCert": "${CA_CERT}", 18 | "server": "${SERVER}", 19 | "privateKey": "${PRIVATE_KEY}" 20 | }, 21 | "2bb8dfa0.48854": { 22 | "user": "${ATLAS_USERNAME}", 23 | "password": "${ATLAS_PASSWORD}" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /nodes/README.md: -------------------------------------------------------------------------------- 1 | To add additional nodes, either: 2 | - drop them in this directory and add their dependencies to ../package.json 3 | - add their npm package name to ../package.json 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodered-heroku", 3 | "version": "1.0.0", 4 | "author": { 5 | "name": "Alerox" 6 | }, 7 | "description": "Node-RED on cloud, deployed in Heroku", 8 | "engines": { 9 | "node": "14.x" 10 | }, 11 | "keywords": [ 12 | "node", 13 | "red", 14 | "cloud", 15 | "heroku" 16 | ], 17 | "dependencies": { 18 | "feedparser": "2.2.10", 19 | "nano": "8.2.2", 20 | "node-red": "latest", 21 | "node-red-contrib-aedes": "latest", 22 | "node-red-contrib-postgresql": "latest", 23 | "node-red-contrib-qrcode-generator": "latest", 24 | "node-red-contrib-string": "latest", 25 | "node-red-contrib-telegrambot-home": "latest", 26 | "node-red-contrib-ui-led": "latest", 27 | "node-red-contrib-ui-media": "latest", 28 | "node-red-contrib-ui-time-scheduler": "latest", 29 | "node-red-dashboard": "latest", 30 | "node-red-node-base64": "latest", 31 | "node-red-node-mongodb": "latest", 32 | "node-red-node-ui-list": "latest", 33 | "node-red-node-ui-table": "latest", 34 | "node-red-contrib-simple-queue":"latest", 35 | "node-red-contrib-cron-plus":"latest", 36 | "node-red-contrib-uuid":"latest", 37 | "node-red-contrib-actionflows":"latest", 38 | "node-red-contrib-mongodb4":"latest", 39 | "node-red-contrib-mongoql":"latest", 40 | "redis": "3.0.2", 41 | "when": "3.7.8" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/css/simplegrid.css: -------------------------------------------------------------------------------- 1 | /* 2 | Simple Grid 3 | Learn More - http://dallasbass.com/simple-grid-a-lightweight-responsive-css-grid/ 4 | Project Page - http://thisisdallas.github.com/Simple-Grid/ 5 | Author - Dallas Bass 6 | Site - dallasbass.com 7 | */ 8 | 9 | *, *:after, *:before { 10 | -webkit-box-sizing: border-box; 11 | -moz-box-sizing: border-box; 12 | box-sizing: border-box; 13 | } 14 | 15 | body { 16 | margin: 0px; 17 | } 18 | 19 | [class*='col-'] { 20 | float: left; 21 | padding-left: 17px; 22 | padding-right: 17px; 23 | } 24 | 25 | [class*='col-']:last-of-type { 26 | padding-right: 0px; 27 | } 28 | [class*='col-']:first-of-type { 29 | padding-left: 0px; 30 | } 31 | 32 | .grid { 33 | width: 100%; 34 | max-width: 1155px; 35 | min-width: 755px; 36 | margin: 0 auto; 37 | overflow: hidden; 38 | padding: 0px 75px 0 75px; 39 | } 40 | 41 | .grid:after { 42 | content: ""; 43 | display: table; 44 | clear: both; 45 | } 46 | 47 | 48 | /* Content Columns */ 49 | 50 | .col-1-1 { 51 | width: 100%; 52 | } 53 | .col-2-3, .col-8-12 { 54 | width: 66.66%; 55 | } 56 | 57 | .col-1-2, .col-6-12 { 58 | width: 50%; 59 | } 60 | 61 | .col-1-3, .col-4-12 { 62 | width: 33.33%; 63 | } 64 | 65 | .col-1-4, .col-3-12 { 66 | width: 25%; 67 | } 68 | 69 | .col-1-5 { 70 | width: 20%; 71 | } 72 | 73 | .col-1-6, .col-2-12 { 74 | width: 16.667%; 75 | } 76 | 77 | .col-1-7 { 78 | width: 14.28%; 79 | } 80 | 81 | .col-1-8 { 82 | width: 12.5%; 83 | } 84 | 85 | .col-1-9 { 86 | width: 11.1%; 87 | } 88 | 89 | .col-1-10 { 90 | width: 10%; 91 | } 92 | 93 | .col-1-11 { 94 | width: 9.09%; 95 | } 96 | 97 | .col-1-12 { 98 | width: 8.33% 99 | } 100 | 101 | /* Layout Columns */ 102 | 103 | .col-11-12 { 104 | width: 91.66% 105 | } 106 | 107 | .col-10-12 { 108 | width: 83.333%; 109 | } 110 | 111 | .col-9-12 { 112 | width: 75%; 113 | } 114 | 115 | .col-5-12 { 116 | width: 41.66%; 117 | } 118 | 119 | .col-7-12 { 120 | width: 58.33% 121 | } 122 | 123 | @media handheld, only screen and (max-width: 767px) { 124 | 125 | 126 | .grid { 127 | width: 100%; 128 | min-width: 0; 129 | margin-left: 0px; 130 | margin-right: 0px; 131 | padding-left: 0px; 132 | padding-right: 0px; 133 | } 134 | 135 | [class*='col-'] { 136 | width: auto; 137 | float: none; 138 | margin-left: 0px; 139 | margin-right: 0px; 140 | margin-top: 10px; 141 | margin-bottom: 10px; 142 | padding-left: 20px !important; 143 | padding-right: 20px !important; 144 | } 145 | } -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #555; 3 | } 4 | a { 5 | text-decoration: none; 6 | } 7 | .title, .row2, .nodes { 8 | background: #eee; 9 | } 10 | 11 | 12 | .blurb a { 13 | text-decoration: underline; 14 | color: #555; 15 | } 16 | a.button1 { 17 | text-decoration: none; 18 | display: inline-block; 19 | padding: 30px 40px; 20 | border-radius: 5px; 21 | background: #aa6767; 22 | color: #eee; 23 | } 24 | a.button1:hover { 25 | background: #7F4545; 26 | } 27 | a.button2 { 28 | text-decoration: none; 29 | display: inline-block; 30 | padding: 30px 40px; 31 | border-radius: 5px; 32 | background: #67aaaa; 33 | color: #eee; 34 | } 35 | a.button2:hover { 36 | background: #457F7F; 37 | } 38 | .row3, .row4, .row5 { 39 | background: #fff; 40 | } 41 | 42 | .row9 { 43 | background: #676767; 44 | } 45 | .footer { 46 | background: #333; 47 | } 48 | 49 | .footer .grid { 50 | color: #eee; 51 | padding: 20px 0; 52 | font-family: Arial; 53 | } 54 | .footer a { 55 | color: #eee; 56 | } 57 | 58 | .footer .content { 59 | height: auto; 60 | min-height: 0; 61 | margin: 20px auto; 62 | } 63 | 64 | .footer .headline { 65 | margin: 80px auto 20px auto; 66 | } 67 | .footer { 68 | text-align: center; 69 | } 70 | 71 | .ets-link { 72 | margin: 10px 0px; 73 | 74 | } 75 | .ets-globe { 76 | width: 60px; 77 | vertical-align: middle; 78 | } 79 | 80 | .content { 81 | margin: 80px 0; 82 | } 83 | 84 | .blurb { 85 | font-family: Arial; 86 | font-size: 16px; 87 | line-height: 1.6em; 88 | } 89 | .blurb p { 90 | margin-top: 0; 91 | } 92 | .blurb h3, .nodes h3 { 93 | font-family: "Roboto Slab"; 94 | font-size: 24px; 95 | font-weight: normal; 96 | margin-top: 0; 97 | margin-bottom: 0.5em; 98 | } 99 | .blurb h4 { 100 | font-family: "Roboto Slab"; 101 | font-size: 18px; 102 | font-weight: normal; 103 | margin-top: 0.8em; 104 | margin-bottom: 0.5em; 105 | } 106 | .feature { 107 | max-width: 485px; 108 | margin-left: auto; 109 | margin-right: auto; 110 | text-align: center; 111 | } 112 | 113 | .row3 .feature, .row4 .feature, .row5 .feature { 114 | border: none; 115 | } 116 | .feature img { 117 | max-width: 445px; 118 | width: 100%; 119 | } 120 | .title .content { 121 | margin: 80px 0 20px 0; 122 | height: 280px; 123 | text-align: center; 124 | } 125 | .title h1 { 126 | font-size: 36px; 127 | font-family: "Roboto Slab"; 128 | font-weight: bold; 129 | margin-bottom: 10px; 130 | color: #676767; 131 | } 132 | .title h2 { 133 | margin-top: 0px; 134 | font-size: 20px; 135 | font-family: "Roboto Slab"; 136 | font-weight: normal; 137 | color: #555; 138 | } 139 | .title img { 140 | margin: auto; 141 | max-width: 769px; 142 | width: 100%; 143 | } 144 | 145 | .nodes .content { 146 | text-align: center; 147 | height: auto; 148 | min-height: 0; 149 | margin: 40px 0 40px 0; 150 | } 151 | .nodes .grid { 152 | padding-top: 40px; 153 | padding-bottom: 40px; 154 | } 155 | 156 | .nodes h3 { 157 | text-align: center; 158 | } 159 | .nodes h4 { 160 | font-family: "Roboto Slab"; 161 | text-align: center; 162 | font-weight: normal; 163 | margin-top: 3px; 164 | height: 50px; 165 | } 166 | 167 | .header { 168 | font-family: "Roboto Slab"; 169 | font-size: 20px; 170 | line-height: 50px; 171 | color: #999; 172 | padding: 0px 10px; 173 | height: 50px; 174 | background: #333; 175 | } 176 | .header-content { 177 | width: 100%; 178 | max-width: 1155px; 179 | min-width: 755px; 180 | margin: 0 auto; 181 | } 182 | .header-content .logo { 183 | vertical-align: middle; 184 | height: 20px; 185 | } 186 | .header-content a { 187 | color: #999; 188 | } 189 | .header-content ul { 190 | float: right; 191 | list-style-type: none; 192 | margin: -0; 193 | padding: 0; 194 | background: #333; 195 | } 196 | .header-content li { 197 | display: inline-block; 198 | margin: 0; 199 | padding: 0; 200 | } 201 | .header-content li a { 202 | display: block; 203 | padding: 0 15px 0 15px; 204 | } 205 | .header-content li.current { 206 | background: url(images/tab.png) 50% bottom no-repeat; 207 | } 208 | .header-content li.current a { 209 | color: #fff; 210 | } 211 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/favicon.ico -------------------------------------------------------------------------------- /public/images/ets-globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/images/ets-globe.png -------------------------------------------------------------------------------- /public/images/node-red-title-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/images/node-red-title-flow.png -------------------------------------------------------------------------------- /public/images/node-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/images/node-red.png -------------------------------------------------------------------------------- /public/images/nr-image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/images/nr-image-1.png -------------------------------------------------------------------------------- /public/images/save-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/images/save-button.png -------------------------------------------------------------------------------- /public/images/tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sevenmojoe/nodered-heroku/8b0a6137d3aa78dcfa8e063e033b5d4a3e2e22af/public/images/tab.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Node-RED in Heroku 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | Node-RED in Heroku 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |

Node-RED in Heroku

29 |

A visual tool for wiring the Internet of Things

30 | 31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |

Node-RED provides a browser-based editor that makes it easy to wire together flows that can be 41 | deployed to the runtime in a single-click.

42 |

The version running here has been customised for Heroku.

43 |

More information about Node-RED, including documentation, can be found at nodered.org.

45 |
46 |
47 | 58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 |

Customising the Node-RED instance

66 |

This template instance of Node-RED is enough to get you started creating flows. 67 |

68 |

You may want to customise it for your needs, 69 | for example replacing this introduction page with your own, adding http authentication to the 70 | flow editor or adding new nodes to 71 | the palette.

72 |

73 |

Password protect the flow editor

74 |

By default, the editor is open for anyone to access and modify flows. To password-protect the 75 | editor:

76 |

Add the following user-defined variables.

77 |
    78 |
  • NODE_RED_USERNAME - the username to secure the editor with
  • 79 |
  • NODE_RED_PASSWORD - the password to secure the editor with
  • 80 |
81 |

Adding new nodes to the palette

82 |
    83 |
  • There is a growing collection of additional nodes that can be added to the Node-RED editor. 84 | You can search for available nodes in the Node-RED library.
  • 86 |
  • The required node package are added in the file package.json to the 87 | dependencies section. The format is: 88 |
    "node-red-package-name":"x.y.x"
    where x.y.z is the desired 89 | version number. 90 |
  • 91 |
  • 92 | See next section to save the added nodes. 93 |
  • 94 |
95 |

Saving deployed flows and installed nodes

96 |
    97 |
  • Heroku doesn't automatically save flows, credentials and installed nodes, so they may be 98 | lost when dynos are restarted. To overcome this, changes must be pushed to GitHub so as 99 | Node-RED is rebuilt correctly with the changed files when Heroku restarts.
  • 100 |
  • To do this, use the SAVE Inject node in the first Flow to push 101 | flows.json, flows_cred.json and package.json to 102 | GitHub, after new flows are deployed and/or new nodes installed into the Palette. 103 |
  • 104 | 105 |
106 |

Upgrading the version of Node-RED

107 |
    108 |
  • This boilerplate is configured to grab the latest stable release of Node-RED whenever the 109 | application is pushed into Heroku.
  • 110 |
111 |

Changing the static web content

112 |
    113 |
  • The page you are reading now is served as static content from the application. This can be 114 | replaced with whatever content you want in the public directory.
  • 115 |
116 |

Remove static web content and serve the flow editor from the root path

117 |
    118 |
  • In the file settings.js, delete the httpStatic and 119 | httpAdminRoot entries. 120 |
  • 121 |
122 |
123 |
124 |
125 |
126 | 136 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 IBM Corp. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | **/ 17 | 18 | var path = require('path'); 19 | var when = require('when'); 20 | 21 | process.env.HOSTNAME = require('os').hostname(); 22 | //process.env.BOT_TOKEN = "2097247350:AAHvGZR34e34Y0MjKtvFRFLs1qcysirD2rA"; 23 | //... useless... use Config Vars in Heroku Dashboard | Settings, instead! 24 | 25 | /** 26 | * This is the default settings file provided by Node-RED. 27 | * 28 | * It can contain any valid JavaScript code that will get run when Node-RED 29 | * is started. 30 | * 31 | * Lines that start with // are commented out. 32 | * Each entry should be separated from the entries above and below by a comma ',' 33 | * 34 | * For more information about individual settings, refer to the documentation: 35 | * https://nodered.org/docs/user-guide/runtime/configuration 36 | * 37 | * The settings are split into the following sections: 38 | * - Flow File and User Directory Settings 39 | * - Security 40 | * - Server Settings 41 | * - Runtime Settings 42 | * - Editor Settings 43 | * - Node Settings 44 | * 45 | **/ 46 | 47 | var settings = module.exports = { 48 | 49 | // [v1.x] Blacklist the non-bluemix friendly nodes 50 | // nodesExcludes:[ '66-mongodb.js','75-exec.js','35-arduino.js','36-rpi-gpio.js','25-serial.js','28-tail.js','50-file.js','31-tcpin.js','32-udp.js','23-watch.js' ], 51 | 52 | // [v1.x] Enable module reinstalls on start-up; this ensures modules installed 53 | // post-deploy are restored after a restage 54 | // autoInstallModules: true, 55 | 56 | // [v1.x] You can protect the user interface with a userid and password by using the following property 57 | // the password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password') 58 | // httpAdminAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, 59 | 60 | /******************************************************************************* 61 | * Flow File and User Directory Settings 62 | * - flowFile 63 | * - credentialSecret 64 | * - flowFilePretty 65 | * - userDir 66 | * - nodesDir 67 | ******************************************************************************/ 68 | 69 | /** The file containing the flows. If not set, defaults to flows_.json **/ 70 | // Never change flow's file 71 | flowFile: 'flows.json', 72 | 73 | /** By default, credentials are encrypted in storage using a generated key. To 74 | * specify your own secret, set the following property. 75 | * If you want to disable encryption of credentials, set this property to false. 76 | * Note: once you set this property, do not change it - doing so will prevent 77 | * node-red from being able to decrypt your existing credentials and they will be 78 | * lost. 79 | */ 80 | //credentialSecret: "a-secret-key", 81 | // Disabled Credential Secret 82 | credentialSecret: false, 83 | 84 | /** By default, the flow JSON will be formatted over multiple lines making 85 | * it easier to compare changes when using version control. 86 | * To disable pretty-printing of the JSON set the following property to false. 87 | */ 88 | flowFilePretty: true, 89 | 90 | /** By default, all user data is stored in a directory called `.node-red` under 91 | * the user's home directory. To use a different location, the following 92 | * property can be used 93 | */ 94 | //userDir: '/home/nol/.node-red/', 95 | 96 | /** Node-RED scans the `nodes` directory in the userDir to find local node files. 97 | * The following property can be used to specify an additional directory to scan. 98 | */ 99 | //nodesDir: '/home/nol/.node-red/nodes', 100 | // Add the nodes in 101 | nodesDir: path.join(__dirname, "nodes"), 102 | 103 | /******************************************************************************* 104 | * Security 105 | * - adminAuth 106 | * - https 107 | * - httpsRefreshInterval 108 | * - requireHttps 109 | * - httpNodeAuth 110 | * - httpStaticAuth 111 | ******************************************************************************/ 112 | 113 | /** To password protect the Node-RED editor and admin API, the following 114 | * property can be used. See http://nodered.org/docs/security.html for details. 115 | */ 116 | //adminAuth: { 117 | // type: "credentials", 118 | // users: [{ 119 | // username: "admin", 120 | // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 121 | // permissions: "*" 122 | // }] 123 | //}, 124 | 125 | /** The following property can be used to enable HTTPS 126 | * This property can be either an object, containing both a (private) key 127 | * and a (public) certificate, or a function that returns such an object. 128 | * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 129 | * for details of its contents. 130 | */ 131 | 132 | /** Option 1: static object */ 133 | //https: { 134 | // key: require("fs").readFileSync('privkey.pem'), 135 | // cert: require("fs").readFileSync('cert.pem') 136 | //}, 137 | 138 | /** Option 2: function that returns the HTTP configuration object */ 139 | // https: function() { 140 | // // This function should return the options object, or a Promise 141 | // // that resolves to the options object 142 | // return { 143 | // key: require("fs").readFileSync('privkey.pem'), 144 | // cert: require("fs").readFileSync('cert.pem') 145 | // } 146 | // }, 147 | 148 | /** If the `https` setting is a function, the following setting can be used 149 | * to set how often, in hours, the function will be called. That can be used 150 | * to refresh any certificates. 151 | */ 152 | //httpsRefreshInterval : 12, 153 | 154 | /** The following property can be used to cause insecure HTTP connections to 155 | * be redirected to HTTPS. 156 | */ 157 | //requireHttps: true, 158 | 159 | /** To password protect the node-defined HTTP endpoints (httpNodeRoot), 160 | * including node-red-dashboard, or the static content (httpStatic), the 161 | * following properties can be used. 162 | * The `pass` field is a bcrypt hash of the password. 163 | * See http://nodered.org/docs/security.html#generating-the-password-hash 164 | */ 165 | //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 166 | //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 167 | 168 | /******************************************************************************* 169 | * Server Settings 170 | * - uiPort 171 | * - uiHost 172 | * - apiMaxLength 173 | * - httpServerOptions 174 | * - httpAdminRoot 175 | * - httpAdminMiddleware 176 | * - httpNodeRoot 177 | * - httpNodeCors 178 | * - httpNodeMiddleware 179 | * - httpStatic 180 | ******************************************************************************/ 181 | 182 | /** the tcp port that the Node-RED web server is listening on */ 183 | uiPort: process.env.PORT || 1880, 184 | 185 | /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. 186 | * To listen on all IPv6 addresses, set uiHost to "::", 187 | * The following property can be used to listen on a specific interface. For 188 | * example, the following would only allow connections from the local machine. 189 | */ 190 | //uiHost: "127.0.0.1", 191 | 192 | /** The maximum size of HTTP request that will be accepted by the runtime api. 193 | * Default: 5mb 194 | */ 195 | //apiMaxLength: '5mb', 196 | 197 | /** The following property can be used to pass custom options to the Express.js 198 | * server used by Node-RED. For a full list of available options, refer 199 | * to http://expressjs.com/en/api.html#app.settings.table 200 | */ 201 | //httpServerOptions: { }, 202 | 203 | /** By default, the Node-RED UI is available at http://localhost:1880/ 204 | * The following property can be used to specify a different root path. 205 | * If set to false, this is disabled. 206 | */ 207 | //httpAdminRoot: '/admin', 208 | // Move the admin UI 209 | httpAdminRoot: '/editor', 210 | 211 | /** The following property can be used to add a custom middleware function 212 | * in front of all admin http routes. For example, to set custom http 213 | * headers. It can be a single function or an array of middleware functions. 214 | */ 215 | // httpAdminMiddleware: function(req,res,next) { 216 | // // Set the X-Frame-Options header to limit where the editor 217 | // // can be embedded 218 | // //res.set('X-Frame-Options', 'sameorigin'); 219 | // next(); 220 | // }, 221 | 222 | /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. 223 | * By default, these are served relative to '/'. The following property 224 | * can be used to specifiy a different root path. If set to false, this is 225 | * disabled. 226 | */ 227 | //httpNodeRoot: '/red-nodes', 228 | 229 | /** The following property can be used to configure cross-origin resource sharing 230 | * in the HTTP nodes. 231 | * See https://github.com/troygoode/node-cors#configuration-options for 232 | * details on its contents. The following is a basic permissive set of options: 233 | */ 234 | //httpNodeCors: { 235 | // origin: "*", 236 | // methods: "GET,PUT,POST,DELETE" 237 | //}, 238 | httpNodeCors: { 239 | origin: "*", 240 | methods: "GET,PUT,POST,DELETE" 241 | }, 242 | 243 | /** If you need to set an http proxy please set an environment variable 244 | * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. 245 | * For example - http_proxy=http://myproxy.com:8080 246 | * (Setting it here will have no effect) 247 | * You may also specify no_proxy (or NO_PROXY) to supply a comma separated 248 | * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk 249 | */ 250 | 251 | /** The following property can be used to add a custom middleware function 252 | * in front of all http in nodes. This allows custom authentication to be 253 | * applied to all http in nodes, or any other sort of common request processing. 254 | * It can be a single function or an array of middleware functions. 255 | */ 256 | //httpNodeMiddleware: function(req,res,next) { 257 | // // Handle/reject the request, or pass it on to the http in node by calling next(); 258 | // // Optionally skip our rawBodyParser by setting this to true; 259 | // //req.skipRawBodyParser = true; 260 | // next(); 261 | //}, 262 | 263 | /** When httpAdminRoot is used to move the UI to a different root path, the 264 | * following property can be used to identify a directory of static content 265 | * that should be served at http://localhost:1880/. 266 | */ 267 | //httpStatic: '/home/nol/node-red-static/', 268 | // Serve up the welcome page 269 | httpStatic: path.join(__dirname, "public"), 270 | 271 | /******************************************************************************* 272 | * Runtime Settings 273 | * - lang 274 | * - logging 275 | * - contextStorage 276 | * - exportGlobalContextKeys 277 | * - externalModules 278 | ******************************************************************************/ 279 | 280 | /** Uncomment the following to run node-red in your preferred language. 281 | * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko 282 | * Some languages are more complete than others. 283 | */ 284 | // lang: "de", 285 | 286 | /** Configure the logging output */ 287 | logging: { 288 | /** Only console logging is currently supported */ 289 | console: { 290 | /** Level of logging to be recorded. Options are: 291 | * fatal - only those errors which make the application unusable should be recorded 292 | * error - record errors which are deemed fatal for a particular request + fatal errors 293 | * warn - record problems which are non fatal + errors + fatal errors 294 | * info - record information about the general running of the application + warn + error + fatal errors 295 | * debug - record information which is more verbose than info + info + warn + error + fatal errors 296 | * trace - record very detailed logging + debug + info + warn + error + fatal errors 297 | * off - turn off all logging (doesn't affect metrics or audit) 298 | */ 299 | //level: "info", 300 | /** Whether or not to include metric events in the log output */ 301 | //metrics: false, 302 | /** Whether or not to include audit events in the log output */ 303 | //audit: false 304 | } 305 | }, 306 | 307 | /** Context Storage 308 | * The following property can be used to enable context storage. The configuration 309 | * provided here will enable file-based context that flushes to disk every 30 seconds. 310 | * Refer to the documentation for further options: https://nodered.org/docs/api/context/ 311 | */ 312 | //contextStorage: { 313 | // default: { 314 | // module:"localfilesystem" 315 | // }, 316 | //}, 317 | 318 | /** `global.keys()` returns a list of all properties set in global context. 319 | * This allows them to be displayed in the Context Sidebar within the editor. 320 | * In some circumstances it is not desirable to expose them to the editor. The 321 | * following property can be used to hide any property set in `functionGlobalContext` 322 | * from being list by `global.keys()`. 323 | * By default, the property is set to false to avoid accidental exposure of 324 | * their values. Setting this to true will cause the keys to be listed. 325 | */ 326 | //exportGlobalContextKeys: false, 327 | exportGlobalContextKeys: true, 328 | 329 | /** Configure how the runtime will handle external npm modules. 330 | * This covers: 331 | * - whether the editor will allow new node modules to be installed 332 | * - whether nodes, such as the Function node are allowed to have their 333 | * own dynamically configured dependencies. 334 | * The allow/denyList options can be used to limit what modules the runtime 335 | * will install/load. It can use '*' as a wildcard that matches anything. 336 | */ 337 | externalModules: { 338 | // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ 339 | // Enable module reinstalls on start-up; this ensures modules installed post-deploy are restored after a restage 340 | autoInstall: true, 341 | // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ 342 | // palette: { /** Configuration for the Palette Manager */ 343 | // allowInstall: true, /** Enable the Palette Manager in the editor */ 344 | // allowUpdate: true, /** Allow modules to be updated in the Palette Manager */ 345 | // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ 346 | // allowList: ['*'], 347 | // denyList: [], 348 | // allowUpdateList: ['*'], 349 | // denyUpdateList: [] 350 | // }, 351 | // modules: { /** Configuration for node-specified modules */ 352 | // allowInstall: true, 353 | // allowList: [], 354 | // denyList: [] 355 | // } 356 | }, 357 | 358 | 359 | /******************************************************************************* 360 | * Editor Settings 361 | * - disableEditor 362 | * - editorTheme 363 | ******************************************************************************/ 364 | 365 | /** The following property can be used to disable the editor. The admin API 366 | * is not affected by this option. To disable both the editor and the admin 367 | * API, use either the httpRoot or httpAdminRoot properties 368 | */ 369 | //disableEditor: false, 370 | 371 | /** Customising the editor 372 | * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes 373 | * for all available options. 374 | */ 375 | editorTheme: { 376 | /** The following property can be used to set a custom theme for the editor. 377 | * See https://github.com/node-red-contrib-themes/theme-collection for 378 | * a collection of themes to chose from. 379 | */ 380 | //theme: "", 381 | palette: { 382 | /** The following property can be used to order the categories in the editor 383 | * palette. If a node's category is not in the list, the category will get 384 | * added to the end of the palette. 385 | * If not set, the following default order is used: 386 | */ 387 | //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], 388 | }, 389 | projects: { 390 | /** To enable the Projects feature, set this value to true */ 391 | enabled: false, 392 | workflow: { 393 | /** Set the default projects workflow mode. 394 | * - manual - you must manually commit changes 395 | * - auto - changes are automatically committed 396 | * This can be overridden per-user from the 'Git config' 397 | * section of 'User Settings' within the editor 398 | */ 399 | mode: "manual" 400 | } 401 | }, 402 | codeEditor: { 403 | /** Select the text editor component used by the editor. 404 | * Defaults to "ace", but can be set to "ace" or "monaco" 405 | */ 406 | //lib: "ace", 407 | lib: "monaco", 408 | options: { 409 | /** The follow options only apply if the editor is set to "monaco" 410 | * 411 | * theme - must match the file name of a theme in 412 | * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme 413 | * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" 414 | */ 415 | theme: "vs", 416 | /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. 417 | * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html 418 | */ 419 | //fontSize: 14, 420 | //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", 421 | //fontLigatures: true, 422 | } 423 | } 424 | }, 425 | 426 | /******************************************************************************* 427 | * Node Settings 428 | * - fileWorkingDirectory 429 | * - functionGlobalContext 430 | * - functionExternalModules 431 | * - nodeMessageBufferMaxLength 432 | * - ui (for use with Node-RED Dashboard) 433 | * - debugUseColors 434 | * - debugMaxLength 435 | * - execMaxBufferSize 436 | * - httpRequestTimeout 437 | * - mqttReconnectTime 438 | * - serialReconnectTime 439 | * - socketReconnectTime 440 | * - socketTimeout 441 | * - tcpMsgQueueSize 442 | * - inboundWebSocketTimeout 443 | * - tlsConfigDisableLocalFiles 444 | * - webSocketNodeVerifyClient 445 | ******************************************************************************/ 446 | 447 | /** The working directory to handle relative file paths from within the File nodes 448 | * defaults to the working directory of the Node-RED process. 449 | */ 450 | //fileWorkingDirectory: "", 451 | 452 | /** Allow the Function node to load additional npm modules directly */ 453 | functionExternalModules: true, 454 | 455 | /** The following property can be used to set predefined values in Global Context. 456 | * This allows extra node modules to be made available with in Function node. 457 | * For example, the following: 458 | * functionGlobalContext: { os:require('os') } 459 | * will allow the `os` module to be accessed in a Function node using: 460 | * global.get("os") 461 | */ 462 | functionGlobalContext: { 463 | // os:require('os'), 464 | }, 465 | 466 | /** The maximum number of messages nodes will buffer internally as part of their 467 | * operation. This applies across a range of nodes that operate on message sequences. 468 | * defaults to no limit. A value of 0 also means no limit is applied. 469 | */ 470 | //nodeMessageBufferMaxLength: 0, 471 | 472 | /** If you installed the optional node-red-dashboard you can set it's path 473 | * relative to httpNodeRoot 474 | * Other optional properties include 475 | * readOnly:{boolean}, 476 | * middleware:{function or array}, (req,res,next) - http middleware 477 | * ioMiddleware:{function or array}, (socket,next) - socket.io middleware 478 | */ 479 | //ui: { path: "ui" }, 480 | // Move the dashboard UI 481 | ui: { path: "/ui" }, 482 | 483 | /** Colourise the console output of the debug node */ 484 | //debugUseColors: true, 485 | 486 | /** The maximum length, in characters, of any message sent to the debug sidebar tab */ 487 | // debugMaxLength: 1000, 488 | debugMaxLength: 10000000, 489 | 490 | /** Maximum buffer size for the exec node. Defaults to 10Mb */ 491 | //execMaxBufferSize: 10000000, 492 | 493 | /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ 494 | //httpRequestTimeout: 120000, 495 | 496 | /** Retry time in milliseconds for MQTT connections */ 497 | mqttReconnectTime: 15000, 498 | 499 | /** Retry time in milliseconds for Serial port connections */ 500 | serialReconnectTime: 15000, 501 | 502 | /** Retry time in milliseconds for TCP socket connections */ 503 | //socketReconnectTime: 10000, 504 | 505 | /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ 506 | //socketTimeout: 120000, 507 | 508 | /** Maximum number of messages to wait in queue while attempting to connect to TCP socket 509 | * defaults to 1000 510 | */ 511 | //tcpMsgQueueSize: 2000, 512 | 513 | /** Timeout in milliseconds for inbound WebSocket connections that do not 514 | * match any configured node. Defaults to 5000 515 | */ 516 | //inboundWebSocketTimeout: 5000, 517 | 518 | /** To disable the option for using local files for storing keys and 519 | * certificates in the TLS configuration node, set this to true. 520 | */ 521 | //tlsConfigDisableLocalFiles: true, 522 | 523 | /** The following property can be used to verify websocket connection attempts. 524 | * This allows, for example, the HTTP request headers to be checked to ensure 525 | * they include valid authentication information. 526 | */ 527 | //webSocketNodeVerifyClient: function(info) { 528 | // /** 'info' has three properties: 529 | // * - origin : the value in the Origin header 530 | // * - req : the HTTP request 531 | // * - secure : true if req.connection.authorized or req.connection.encrypted is set 532 | // * 533 | // * The function should return true if the connection should be accepted, false otherwise. 534 | // * 535 | // * Alternatively, if this function is defined to accept a second argument, callback, 536 | // * it can be used to verify the client asynchronously. 537 | // * The callback takes three arguments: 538 | // * - result : boolean, whether to accept the connection or not 539 | // * - code : if result is false, the HTTP error status to return 540 | // * - reason: if result is false, the HTTP reason string to return 541 | // */ 542 | //}, 543 | } 544 | 545 | if (process.env.NODE_RED_USERNAME && process.env.NODE_RED_PASSWORD) { 546 | settings.adminAuth = { 547 | type: "credentials", 548 | users: function (username) { 549 | if (process.env.NODE_RED_USERNAME == username) { 550 | return when.resolve({ username: username, permissions: "*" }); 551 | } else { 552 | return when.resolve(null); 553 | } 554 | }, 555 | authenticate: function (username, password) { 556 | if (process.env.NODE_RED_USERNAME == username && 557 | process.env.NODE_RED_PASSWORD == password) { 558 | return when.resolve({ username: username, permissions: "*" }); 559 | } else { 560 | return when.resolve(null); 561 | } 562 | } 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /utils/file-explorer-flow.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1f0274a4dfdeb367", 4 | "type": "tab", 5 | "label": "FS", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "53035f704bb7e6be", 11 | "type": "inject", 12 | "z": "1f0274a4dfdeb367", 13 | "name": "Init", 14 | "props": [ 15 | { 16 | "p": "payload" 17 | }, 18 | { 19 | "p": "topic", 20 | "vt": "str" 21 | } 22 | ], 23 | "repeat": "", 24 | "crontab": "", 25 | "once": true, 26 | "onceDelay": 0.1, 27 | "topic": "change", 28 | "payload": "/app", 29 | "payloadType": "str", 30 | "x": 90, 31 | "y": 340, 32 | "wires": [ 33 | [ 34 | "be8f219082382d87", 35 | "85090a46f8fa54c4" 36 | ] 37 | ] 38 | }, 39 | { 40 | "id": "ced80261c7808b0f", 41 | "type": "ui_button", 42 | "z": "1f0274a4dfdeb367", 43 | "name": "", 44 | "group": "160e81fb.f1c86e", 45 | "order": 5, 46 | "width": 2, 47 | "height": 1, 48 | "passthru": true, 49 | "label": "Refresh", 50 | "tooltip": "", 51 | "color": "", 52 | "bgcolor": "", 53 | "className": "", 54 | "icon": "refresh", 55 | "payload": "", 56 | "payloadType": "str", 57 | "topic": "refresh", 58 | "topicType": "str", 59 | "x": 80, 60 | "y": 160, 61 | "wires": [ 62 | [ 63 | "be8f219082382d87" 64 | ] 65 | ] 66 | }, 67 | { 68 | "id": "31aa125dbda0cefa", 69 | "type": "ui_button", 70 | "z": "1f0274a4dfdeb367", 71 | "name": "", 72 | "group": "160e81fb.f1c86e", 73 | "order": 4, 74 | "width": 2, 75 | "height": 1, 76 | "passthru": false, 77 | "label": "Delete", 78 | "tooltip": "", 79 | "color": "", 80 | "bgcolor": "", 81 | "icon": "delete", 82 | "payload": "", 83 | "payloadType": "str", 84 | "topic": "", 85 | "x": 70, 86 | "y": 400, 87 | "wires": [ 88 | [ 89 | "f7cd5aa1b1cc81c8" 90 | ] 91 | ] 92 | }, 93 | { 94 | "id": "f7cd5aa1b1cc81c8", 95 | "type": "function", 96 | "z": "1f0274a4dfdeb367", 97 | "name": "Get file", 98 | "func": "// Get the filename from the flow context\nlet filename = flow.get(\"fileselected\");\n\n// check, if the filename is undefined that means it does not exist yet, nothing is selected yet\n// return: do not output anything\nif (filename === undefined) {\n return;\n}\n\n// return the filename to the file-in node to delete\nmsg.filename = filename;\n\nif (msg.filename.replace(/^.*(\\\\|\\/|\\:)/, '')[0] !== \".\") {\n // Only do this if this is a file, we don't delete folders\n return msg;\n}", 99 | "outputs": 1, 100 | "noerr": 0, 101 | "initialize": "", 102 | "finalize": "", 103 | "libs": [], 104 | "x": 280, 105 | "y": 400, 106 | "wires": [ 107 | [ 108 | "0b135f82c4167c37" 109 | ] 110 | ] 111 | }, 112 | { 113 | "id": "9abed70c1a84e163", 114 | "type": "file", 115 | "z": "1f0274a4dfdeb367", 116 | "name": "Delete file", 117 | "filename": "", 118 | "appendNewline": true, 119 | "createDir": false, 120 | "overwriteFile": "delete", 121 | "encoding": "none", 122 | "x": 940, 123 | "y": 400, 124 | "wires": [ 125 | [] 126 | ] 127 | }, 128 | { 129 | "id": "6d1fb9900e550934", 130 | "type": "http in", 131 | "z": "1f0274a4dfdeb367", 132 | "name": "", 133 | "url": "/download", 134 | "method": "get", 135 | "upload": false, 136 | "swaggerDoc": "", 137 | "x": 100, 138 | "y": 520, 139 | "wires": [ 140 | [ 141 | "f4845b5f01b3459e" 142 | ] 143 | ] 144 | }, 145 | { 146 | "id": "4426891007434b2f", 147 | "type": "http response", 148 | "z": "1f0274a4dfdeb367", 149 | "name": "", 150 | "statusCode": "", 151 | "headers": {}, 152 | "x": 790, 153 | "y": 520, 154 | "wires": [] 155 | }, 156 | { 157 | "id": "f4845b5f01b3459e", 158 | "type": "function", 159 | "z": "1f0274a4dfdeb367", 160 | "name": "Extract file", 161 | "func": "msg.filename = msg.req.query.filename;\nmsg.contentdisposition = \"attachment; filename=\\\"\" + msg.req.query.filename.replace(/^.*(\\\\|\\/|\\:)/, '') + \"\\\"\";\nreturn msg;\n", 162 | "outputs": 1, 163 | "noerr": 0, 164 | "initialize": "", 165 | "finalize": "", 166 | "libs": [], 167 | "x": 290, 168 | "y": 520, 169 | "wires": [ 170 | [ 171 | "61f9b4a564ad6ffc" 172 | ] 173 | ], 174 | "outputLabels": [ 175 | "Folder selected" 176 | ] 177 | }, 178 | { 179 | "id": "61f9b4a564ad6ffc", 180 | "type": "file in", 181 | "z": "1f0274a4dfdeb367", 182 | "name": "", 183 | "filename": "", 184 | "format": "", 185 | "chunk": false, 186 | "sendError": false, 187 | "encoding": "none", 188 | "x": 440, 189 | "y": 520, 190 | "wires": [ 191 | [ 192 | "301011cc86b7bb5a" 193 | ] 194 | ] 195 | }, 196 | { 197 | "id": "301011cc86b7bb5a", 198 | "type": "change", 199 | "z": "1f0274a4dfdeb367", 200 | "name": "set headers", 201 | "rules": [ 202 | { 203 | "t": "set", 204 | "p": "headers", 205 | "pt": "msg", 206 | "to": "{}", 207 | "tot": "json" 208 | }, 209 | { 210 | "t": "set", 211 | "p": "headers.content-type", 212 | "pt": "msg", 213 | "to": "text/csv", 214 | "tot": "str" 215 | }, 216 | { 217 | "t": "set", 218 | "p": "headers.Content-Disposition", 219 | "pt": "msg", 220 | "to": "contentdisposition", 221 | "tot": "msg" 222 | } 223 | ], 224 | "action": "", 225 | "property": "", 226 | "from": "", 227 | "to": "", 228 | "reg": false, 229 | "x": 630, 230 | "y": 520, 231 | "wires": [ 232 | [ 233 | "4426891007434b2f" 234 | ] 235 | ] 236 | }, 237 | { 238 | "id": "0c138e847437e572", 239 | "type": "ui_button", 240 | "z": "1f0274a4dfdeb367", 241 | "name": "", 242 | "group": "160e81fb.f1c86e", 243 | "order": 6, 244 | "width": 2, 245 | "height": 1, 246 | "passthru": false, 247 | "label": "Graph", 248 | "tooltip": "", 249 | "color": "", 250 | "bgcolor": "", 251 | "icon": "show_chart", 252 | "payload": "", 253 | "payloadType": "str", 254 | "topic": "", 255 | "topicType": "str", 256 | "x": 70, 257 | "y": 460, 258 | "wires": [ 259 | [ 260 | "49f1528d3aa277ab" 261 | ] 262 | ] 263 | }, 264 | { 265 | "id": "49f1528d3aa277ab", 266 | "type": "function", 267 | "z": "1f0274a4dfdeb367", 268 | "name": "Get file", 269 | "func": "// Get the filename from the flow context\nlet filename = flow.get(\"fileselected\");\n\n// check, if the filename is undefined that means it does not exist yet, nothing is selected yet\n// return: do not output anything\nif (filename === undefined) {\n return;\n}\n\n// return the filename to the file-in node to delete\nmsg.filename = filename;\n\nif (msg.filename.replace(/^.*(\\\\|\\/|\\:)/, '')[0] !== \".\") {\n // Only do this if this is a file, we don't delete folders\n return msg;\n}", 270 | "outputs": 1, 271 | "noerr": 0, 272 | "initialize": "", 273 | "finalize": "", 274 | "libs": [], 275 | "x": 280, 276 | "y": 460, 277 | "wires": [ 278 | [ 279 | "e04a6b965975de4c" 280 | ] 281 | ] 282 | }, 283 | { 284 | "id": "e04a6b965975de4c", 285 | "type": "file in", 286 | "z": "1f0274a4dfdeb367", 287 | "name": "", 288 | "filename": "", 289 | "format": "utf8", 290 | "chunk": false, 291 | "sendError": false, 292 | "encoding": "none", 293 | "x": 440, 294 | "y": 460, 295 | "wires": [ 296 | [ 297 | "3d4282e9e0897475" 298 | ] 299 | ] 300 | }, 301 | { 302 | "id": "3d4282e9e0897475", 303 | "type": "csv", 304 | "z": "1f0274a4dfdeb367", 305 | "name": "", 306 | "sep": ",", 307 | "hdrin": true, 308 | "hdrout": "", 309 | "multi": "mult", 310 | "ret": "\\n", 311 | "temp": "", 312 | "skip": "0", 313 | "strings": true, 314 | "include_empty_strings": false, 315 | "include_null_values": false, 316 | "x": 610, 317 | "y": 460, 318 | "wires": [ 319 | [ 320 | "ac74a4bce6617ee6" 321 | ] 322 | ] 323 | }, 324 | { 325 | "id": "ac74a4bce6617ee6", 326 | "type": "function", 327 | "z": "1f0274a4dfdeb367", 328 | "name": "Plot", 329 | "func": "var chart = [{\n \"series\":[],\n \"data\":[],\n \"labels\":[msg.filename]\n}];\n\n\n/*\nvar pressure = [];\nvar out2 = [];\n\nfor (var i=0; i