Compare commits

..

128 Commits

Author SHA1 Message Date
Nick O'Leary
9932d34304 Fix XML parse test
Workaround to https://github.com/Leonidas-from-XIV/node-xml2js/issues/239
2015-09-29 14:39:07 +01:00
Nick O'Leary
7aa37a1976 Bump version 2015-09-29 10:17:45 +01:00
Dave Conway-Jones
fa42fbdab8 Let XML node options be set
let msg.options to set a lot more options if required
2015-09-26 13:47:14 +01:00
Nick O'Leary
caa83ac830 Merge pull request #724 from vielmetti/travis-node-4
Support for Node 4.0.0 and Travis CI testing for same
2015-09-25 23:02:49 +01:00
Nick O'Leary
3963fa9738 Allow a language catalog to be a partial catalog 2015-09-24 21:56:45 +01:00
Edward Vielmetti
005a98d020 Update for node 4 testing.
Changes to .travis.yml
Patch from @dceejay to make a test portable
Patch from @dceejay to correct a case where an int was expected
2015-09-23 15:27:45 -04:00
Dave Conway-Jones
9560dc9408 remove delay spinner upper limit
e.g. 65 secs is perfectly valid…
close #728
2015-09-22 15:06:58 +01:00
Nick O'Leary
4ac9a5edf0 Merge pull request #726 from vielmetti/patch-2
Bump uglify to 2.4.24 to address security advisory
2015-09-22 10:15:54 +01:00
Edward Vielmetti
37e62597ae Bump uglify to 2.4.24 to address security advisory
Noted in https://nodesecurity.io/advisories/uglifyjs_incorrectly_handles_non-boolean_comparisons
2015-09-20 01:21:21 -04:00
Dave Conway-Jones
90bfe378d0 Add mobile category to palette order 2015-09-16 22:35:17 +01:00
Dave Conway-Jones
ce22b494ec Update README to use svg badges
to close #720
Thanks to @scherwi for the tip
2015-09-08 21:47:57 +01:00
Nick O'Leary
f9e0420647 Fix http node method-override nls message id 2015-08-31 16:06:00 +01:00
Dave Conway-Jones
2fe568d9ba Fix TCP node sending data
(it’s for the children)
2015-08-17 17:15:44 +01:00
Dave Conway-Jones
2d4979df4d only set tcp timeout if needs setting. 2015-08-15 22:16:48 +01:00
Dave Conway-Jones
b555b014b8 Update debug test to check lengths... 2015-08-12 21:56:19 +01:00
Dave Conway-Jones
999b888c54 debug nodes - show length of strings, buffers or size of arrays 2015-08-11 19:39:37 +01:00
Nick O'Leary
5193d7bddb Stop sending messages to ws connections that have errored
Fixes #708
2015-07-30 22:09:01 +01:00
Nick O'Leary
6b03379e4e Ensure exclusive conf node is removed on edit cancel
- If an exclusive conf node was added to a node, but the
   node's own edit dialog was canceled, the conf node remained
   but not associated with the node - effectively orphaning it
2015-07-22 22:28:30 +01:00
Nick O'Leary
08d687ad60 Update to travis container builds 2015-07-22 13:06:14 +01:00
Nick O'Leary
eb57089f06 Add flag to disable build check for tests 2015-07-22 11:41:58 +01:00
Nick O'Leary
a76e4fede1 Handle null acceptedLanguages
Closes #704
2015-07-19 22:11:25 +01:00
Nick O'Leary
705d043540 Replace bootstrap popover 2015-07-18 21:27:16 +01:00
Nick O'Leary
5462e251f8 Fix exclusive config node check when type not registered 2015-07-15 13:23:05 +01:00
Nick O'Leary
50788af6ca Add 0.12 to engine statement 2015-07-15 10:50:38 +01:00
Nick O'Leary
1a07c5a329 Bump 0.11.0 2015-07-15 10:48:33 +01:00
Nick O'Leary
9fb81b2814 Permit login with blank scope
Required for the editor, which doesn't know the appropriate scope
for the user logging in. The user will adopt their default permission
scope once logged in.
2015-07-15 10:12:45 +01:00
Dave Conway-Jones
10ad7fbf6e remove spaces before ? in messages 2015-07-14 23:47:58 +01:00
Nick O'Leary
811c4f2630 Ensure locales dir is included in release zip 2015-07-14 20:37:49 +01:00
Dave Conway-Jones
f7e3b0a64f Tiny pixel tweaks to new ui 2015-07-14 20:37:26 +01:00
Nick O'Leary
7d83d76fb3 Merge pull request #702 from node-red/ui-refresh
UI Refresh
2015-07-14 16:11:48 +01:00
Nick O'Leary
d3c41b38f7 More restylin 2015-07-14 15:59:56 +01:00
Nick O'Leary
57d6b16d5c Reset sidebar font size 2015-07-13 23:30:57 +01:00
Nick O'Leary
27aa5ae7db More UI refresh 2015-07-13 23:21:03 +01:00
Nick O'Leary
62e8f564b9 Fix node import error nls message 2015-07-13 16:28:23 +01:00
Nick O'Leary
a1d7bb4208 More restyling - workspace buttons 2015-07-13 15:08:17 +01:00
Nick O'Leary
5d8dae05c4 More ui redesign 2015-07-13 11:26:29 +01:00
Nick O'Leary
6bde07b5a0 Refresh appearance 2015-07-11 23:43:45 +01:00
Nick O'Leary
846ab08661 Allow node modules to declare supported versions of node-red 2015-07-10 21:42:14 +01:00
Nick O'Leary
999cf66b27 Add editor events component and migrate to it 2015-07-10 19:49:53 +01:00
Dave Conway-Jones
60539d890b Fix File node check of msg.payload to close #700
Also add feature to allow creation of directory(ies) if path to file
does not exist.
2015-07-10 14:59:23 +01:00
Nick O'Leary
e5a0f25d94 Keep sidebar tab menu in alphabetical order 2015-07-09 16:48:53 +01:00
Nick O'Leary
fde9d40098 Copy/clone config nodes properly on import/export 2015-07-08 22:12:52 +01:00
Nick O'Leary
f70e9ea076 Ensure RED._ is defined before initialising settings
- remove permissions requirement from locales files so the
   login dialog can be nls'd
2015-07-08 17:08:07 +01:00
Dave Conway-Jones
d0af4aac4d cleanup status on delay node 2015-07-08 15:06:46 +01:00
Dave Conway-Jones
7de3704210 lets exec node pass more than just string as the command payload. 2015-07-08 15:06:08 +01:00
Nick O'Leary
1c33b837b8 Fix delay node labels 2015-07-08 08:04:47 +01:00
Nick O'Leary
95d20d7fba Ensure status messages are strings 2015-07-08 08:02:23 +01:00
Nick O'Leary
90b8806e7c Merge pull request #697 from knolleary/node12
Add node 0.12 to Travis config and fix resulting errors
2015-07-05 23:11:19 +01:00
Nick O'Leary
39df80bf99 Fix exec test to restart helper server before each test 2015-07-05 23:02:10 +01:00
Nick O'Leary
bac4beae03 Fix Function error parsing for node 0.12 format 2015-07-05 22:40:24 +01:00
Nick O'Leary
a7b68c18b5 Bump fs-extra version to fix 0.12 support 2015-07-05 22:10:34 +01:00
Nick O'Leary
ef2360baee Add node 0.12 to Travis config 2015-07-04 22:08:58 +01:00
Nick O'Leary
8716e7e601 Ensure tab minimum size not applied when there is space for all 2015-07-03 20:54:31 +01:00
Nick O'Leary
00c2dae969 Set minimum size on workspace tab 2015-07-03 18:31:37 +01:00
Nick O'Leary
8782bc5896 Ensure sidebar panes are hidden, not removed on change 2015-07-03 11:17:27 +01:00
Nick O'Leary
6359b90352 Add sidebar menu and migrate existing panels to new api 2015-07-03 10:07:40 +01:00
Nick O'Leary
6cfa4976fe Fix nodes.install.installing message id 2015-07-02 13:25:15 +01:00
Nick O'Leary
35cd7cf2b8 Fix tcp status message 2015-07-02 10:56:27 +01:00
Nick O'Leary
b2d7f079b7 Merge pull request #694 from node-red/i18n
Add i18n support
2015-07-02 10:54:22 +01:00
Nick O'Leary
726069bc4b NLS status text in editor not runtime 2015-07-02 10:49:40 +01:00
Nick O'Leary
fbccf01933 Tidy up red.js runtime messages 2015-07-02 10:49:40 +01:00
Nick O'Leary
c9f3c6f4a3 Update trigger node ui 2015-07-02 10:49:40 +01:00
Nick O'Leary
c47da013ff Tidy up of editor i18n messages 2015-07-02 10:49:40 +01:00
Allen Boone
bc76499957 Use core category for custom nodes if core nodes disabled 2015-07-02 10:49:40 +01:00
Allen Boone
24afcff0ea Custom nodes category NLS 2015-07-02 10:49:40 +01:00
Nick O'Leary
6777f24845 Ensure library export dialog is i18n'd 2015-07-02 10:49:40 +01:00
Nick O'Leary
61e0923fc4 Restore when module dependency in red/api/nodes 2015-07-02 10:49:40 +01:00
Nick O'Leary
757caeb9a4 Try to use default palette label nls message if found 2015-07-02 10:49:40 +01:00
Allen Boone
fe9dc6b272 Palette category label NLS for core nodes 2015-07-02 10:49:40 +01:00
Nick O'Leary
24efdbe4da Add missing-type install hint nls messages 2015-07-02 10:49:39 +01:00
Nick O'Leary
9ca102cf81 Ensure error messages are toStringed 2015-07-02 10:49:39 +01:00
Nick O'Leary
48df31d7b7 NLS deprecated node message 2015-07-02 10:49:39 +01:00
Nick O'Leary
539afb1e1d Fix Inject node interval label 2015-07-02 10:49:39 +01:00
Nick O'Leary
bdcba44ca5 Remove moved node messages 2015-07-02 10:49:39 +01:00
Nick O'Leary
99a51b07ac Catch error loop detection nls 2015-07-02 10:49:39 +01:00
Allen Boone
5fbaca75b4 Fixed sidebar id issue 2015-07-02 10:49:39 +01:00
Allen Boone
a6974371b0 Fixed acceptedLanguage typo 2015-07-02 10:49:39 +01:00
Allen Boone
409fa49234 Added missing NLS strings to sidebar and index.mst 2015-07-02 10:49:39 +01:00
Nick O'Leary
9bbd6a70b8 Add namespaced i18n function to node definition 2015-07-02 10:49:39 +01:00
Nick O'Leary
f0b4cb608a Return locale specific node help 2015-07-02 10:49:39 +01:00
Allen Boone
284d7e26d1 Initialize list of supported languages from directories in the locale folder. 2015-07-02 10:49:39 +01:00
Scott Yoshizawa
965c0937ac NLS undo previous change (added require for ./red/log) 2015-07-02 10:49:39 +01:00
Nick O'Leary
d9cf6a4431 Only attempt to load one locale per catalog 2015-07-02 10:49:39 +01:00
Allen Boone
e6ed8ee509 locale exposed to editor 2015-07-02 10:49:39 +01:00
Scott Yoshizawa
2563649b3e NLS /red/nodes
NLS other js files under /red

NLS /red files (changed based on Nick's review)
2015-07-02 10:49:39 +01:00
Nick O'Leary
203bc41b06 Some more node i18n tidy up 2015-07-02 10:49:38 +01:00
Allen Boone
cb1d18c7c8 Fixed problem with RED._ being unavailable to module code 2015-07-02 10:49:38 +01:00
Nick O'Leary
5ea68dafc4 More node i18n tidy ups 2015-07-02 10:49:38 +01:00
Nick O'Leary
68bb8252af Ensure i18n func available to new config nodes 2015-07-02 10:49:38 +01:00
Nick O'Leary
406f742d29 Add jsonlint step to build 2015-07-02 10:49:38 +01:00
Nick O'Leary
5522e57f65 More node i18n tidy-up 2015-07-02 10:49:38 +01:00
Nick O'Leary
94e27dbfc5 Tidy up node i18n 2015-07-02 10:49:38 +01:00
Nick O'Leary
f5fc8f763f Pick up desired language from query string 2015-07-02 10:49:38 +01:00
Nick O'Leary
9058bf615c Fix invalid json in message catalog 2015-07-02 10:49:38 +01:00
Nick O'Leary
9d17137dec Ensure i18n message for Unknown nodes are available 2015-07-02 10:49:38 +01:00
Allen Boone
4a318553f7 Updated editor ui NLS strings 2015-07-02 10:49:38 +01:00
Allen Boone
0017074d38 Updated the notifications and errors to use NLS
removed errant comma
2015-07-02 10:49:36 +01:00
Allen Boone
a39a26fcc2 Converted editor menu to use NLS strings 2015-07-02 10:47:02 +01:00
Scott Yoshizawa
2fe859b111 NLS Core nodes
NLS exec node

NLS function/temple/delay nodes

NLS function/template/delay/trigger/comment nodes

NLS io nodes (mqtt/httpin/websocket/watch/serial)

NLS messages.json for tcpin

NLS io nodes (tcpin & udp half)

NLS io nodes (udp)

NLS logic nodes (switch/change)

NLS logic (range) and parsers (csv&html) nodes

NLS parser nodes (json/xml)

NLS test case update for logic/parsers

NLS analysis and hardware nodes

NLS storage nodes (file/redisout/mongodb) and test

NLS storage node (tail)

NLS social nodes (feedparse/email/irc)

NLS socal node (twitter half change)

NLS social node (twitter) and core node (unknown)
2015-07-02 10:46:57 +01:00
Nick O'Leary
c105b2df37 Add RED._ to test helper 2015-06-29 16:04:00 +01:00
Nick O'Leary
4fb86ab55a Fix !=0 comparison 2015-06-29 16:04:00 +01:00
Nick O'Leary
1ed98a5963 Dont create i18n function for subflows 2015-06-29 16:04:00 +01:00
Nick O'Leary
a4a29ceb3c Handle [html] data-i18n prefixes in config node dialog 2015-06-29 16:04:00 +01:00
Nick O'Leary
6249083431 Extract all core runtime messages 2015-06-29 16:04:00 +01:00
Nick O'Leary
aa18c65fa8 NLS enable the Inject node 2015-06-29 16:03:59 +01:00
Nick O'Leary
a7900940da Expose i18n in editor 2015-06-29 16:03:59 +01:00
Nick O'Leary
008bc98070 Ensure node help is blank if otherwise undefined 2015-06-29 16:03:59 +01:00
Nick O'Leary
0705589cc2 Load base locales in editor 2015-06-29 16:03:59 +01:00
Nick O'Leary
b2caba593f Add locales api endpoint 2015-06-29 16:03:59 +01:00
Nick O'Leary
6d4c64fcd5 i18n enable runtime node files 2015-06-29 16:03:59 +01:00
Nick O'Leary
7d41781fb4 Add initial red/i18n implementation 2015-06-29 16:03:59 +01:00
Nick O'Leary
0760facb77 Add .DS_store to gitignore 2015-06-29 16:02:01 +01:00
Nick O'Leary
a0ce095807 Error generating complete flow library list on OSX
Must use path.join to concatenate paths safely.
2015-06-29 16:00:10 +01:00
Nick O'Leary
df0110913a Remove rogue console.logs 2015-06-25 15:33:39 -07:00
Nick O'Leary
06731374a4 Fix trigger extend behaviour 2015-06-25 13:59:26 -07:00
Dave Conway-Jones
6c8b7c0082 Correct RPi pins labels... and add BCM pins also. 2015-06-24 12:39:27 +01:00
Dave Conway-Jones
93136961b9 make unspecified settings file totally obvious in debug. 2015-06-22 14:17:16 +01:00
Dave Conway-Jones
529a691e1d Add clearTimeout to function node sandbox
(to be a pair with setTimeout…)
2015-06-22 09:07:31 +01:00
Dave Conway-Jones
9f0b3eba47 Add basic exec node test 2015-06-22 08:26:38 +01:00
Nick O'Leary
6d897793cb Ensure node.outputs is always a number
Closes #686
2015-06-19 20:37:12 +01:00
Nick O'Leary
0e12fc6b02 Fix canvas focus issue on FF/IE 2015-06-17 22:52:04 +01:00
Nick O'Leary
c00558ea1b Remove old index.html 2015-06-17 22:46:54 +01:00
Dave Conway-Jones
bacf27a3ca mistook 2015-06-17 22:12:48 +01:00
Dave Conway-Jones
6560ea0630 Delete Dockerfile
mistook
2015-06-17 22:12:27 +01:00
Nick O'Leary
8338231ce5 Drop blank port from comms ws path 2015-06-17 22:09:27 +01:00
Dave Conway-Jones
5813a91244 adjust timing on file tail test
Runs cleaner on Mac
2015-06-17 22:08:53 +01:00
Nick O'Leary
dfd8ab3545 Detect mouse paste in Import nodes dialog 2015-06-17 20:48:56 +01:00
Nick O'Leary
a5b9b949a8 bump to 0.10.11 dev version 2015-06-17 14:58:22 +01:00
136 changed files with 11503 additions and 3123 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.DS_store
.config.json
.dist
.jshintignore
@@ -14,3 +15,5 @@ flows*.json
nodes/node-red-nodes/
node_modules
public
locales/zz-ZZ
nodes/core/locales/zz-ZZ

View File

@@ -1,7 +1,24 @@
sudo: false
language: node_js
env:
- CXX="g++-4.8"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- gcc-4.8
matrix:
allow_failures:
- node_js: "4.1.0"
before_install:
- npm install -g npm@~1.4.18
# node_js 4.1.0 is brand new
node_js:
- "4.1.0"
- "4.0.0"
- "0.12"
- "0.10"
script:
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage

View File

@@ -93,6 +93,8 @@ module.exports = function(grunt) {
// Ensure editor source files are concatenated in
// the right order
"editor/js/main.js",
"editor/js/events.js",
"editor/js/i18n.js",
"editor/js/settings.js",
"editor/js/user.js",
"editor/js/comms.js",
@@ -104,6 +106,7 @@ module.exports = function(grunt) {
"editor/js/ui/menu.js",
"editor/js/ui/keyboard.js",
"editor/js/ui/tabs.js",
"editor/js/ui/popover.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
"editor/js/ui/sidebar.js",
@@ -128,7 +131,8 @@ module.exports = function(grunt) {
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"editor/vendor/marked/marked.min.js",
"editor/vendor/orion/built-editor.min.js",
"editor/vendor/d3/d3.v3.min.js"
"editor/vendor/d3/d3.v3.min.js",
"editor/vendor/i18next/i18next.min.js"
],
"public/vendor/vendor.css": [
"editor/vendor/orion/built-editor.css"
@@ -153,9 +157,22 @@ module.exports = function(grunt) {
files: [{
dest: 'public/red/style.min.css',
src: 'editor/sass/style.scss'
},
{
dest: 'public/vendor/bootstrap/css/bootstrap.min.css',
src: 'editor/vendor/bootstrap/css/bootstrap.css'
}]
}
},
jsonlint: {
messages: {
src: [
'nodes/core/locales/en-US/messages.json',
'locales/en-US/editor.json',
'locales/en-US/runtime.json'
]
}
},
attachCopyright: {
js: {
src: [
@@ -196,6 +213,14 @@ module.exports = function(grunt) {
'editor/sass/**/*.scss'
],
tasks: ['sass','attachCopyright:css']
},
json: {
files: [
'nodes/core/locales/en-US/messages.json',
'locales/en-US/editor.json',
'locales/en-US/runtime.json'
],
tasks: ['jsonlint:messages']
}
},
@@ -205,7 +230,7 @@ module.exports = function(grunt) {
script: 'red.js',
options: {
args:['-v'],
ext: 'js,html',
ext: 'js,html,json',
watch: [
'red','nodes'
]
@@ -234,7 +259,7 @@ module.exports = function(grunt) {
cwd: 'editor/vendor',
src: [
'ace/**',
'bootstrap/css/**',
//'bootstrap/css/**',
'bootstrap/img/**',
'jquery/css/**',
'font-awesome/**'
@@ -271,7 +296,8 @@ module.exports = function(grunt) {
'red/**',
'public/**',
'editor/templates/**',
'bin/**'
'bin/**',
'locales/**'
],
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
}]
@@ -282,7 +308,7 @@ module.exports = function(grunt) {
mode: '755'
},
release: {
// Target-specific file/dir lists and/or options go here.
// Target-specific file/dir lists and/or options go here.
src: [
path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>/nodes/core/hardware/nrgpio*')
]
@@ -312,7 +338,8 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-chmod');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src;
var copyright = "/**\n"+
@@ -377,7 +404,7 @@ module.exports = function(grunt) {
grunt.registerTask('build',
'Builds editor content',
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','copy:build','attachCopyright']);
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','jsonlint:messages','copy:build','attachCopyright']);
grunt.registerTask('dev',
'Developer mode: run node-red, watch for source changes and build/restart',

View File

@@ -2,8 +2,8 @@
http://nodered.org
[![Build Status](https://travis-ci.org/node-red/node-red.png)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.png?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
[![Build Status](https://travis-ci.org/node-red/node-red.svg)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.svg?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
A visual tool for wiring the Internet of Things.
@@ -67,4 +67,3 @@ For more open-source projects from IBM, head over [here](http://ibm.github.io).
## Copyright and license
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

After

Width:  |  Height:  |  Size: 192 B

View File

@@ -1,177 +0,0 @@
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<!--
Copyright 2013, 2015 IBM Corp.
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.
-->
<head>
<title>Node-RED</title>
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="vendor/jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="vendor/vendor.css">
<link rel="stylesheet" href="red/style.min.css">
</head>
<body spellcheck="false">
<div id="header">
<span class="logo"><img src="red/images/node-red.png"> <span>Node-RED</span></span>
<ul class="header-toolbar hide">
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
<ul>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-container" class="palette-scroll">
</div>
<div id="palette-search">
<i class="fa fa-search"></i><input id="palette-search-input" type="text" placeholder="filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
</div>
</div><!-- /palette -->
<div id="workspace">
<ul id="workspace-tabs"></ul>
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart"></div>
<div id="workspace-toolbar">
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a>
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a>
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a>
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</a>
</div>
</div>
<div id="chart-zoom-controls">
<div class="btn-group">
<a class="btn btn-mini" id="btn-zoom-out" href="#"><i class="fa fa-search-minus"></i></a>
<a class="btn btn-mini" id="btn-zoom-zero" href="#"><i class="fa fa-dot-circle-o"></i></a>
<a class="btn btn-mini" id="btn-zoom-in" href="#"><i class="fa fa-search-plus"></i></a>
</div>
</div>
<div id="sidebar">
<ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div>
</div>
<div id="sidebar-separator"></div>
</div>
<div id="notifications"></div>
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
<div id="subflow-dialog" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label>Name</label><input type="text" id="subflow-input-name">
</div>
</form>
<div class="form-tips" id="subflow-dialog-user-count"></div>
</div>
<div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: center; padding-top: 30px;">
Some of the nodes are not properly configured. Are you sure you want to deploy?
</div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: center; padding-top: 10px;">
The workspace contains some unknown node types:
<ul style="width: 300px; margin: auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
Are you sure you want to deploy?
</div>
</form>
</div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
A <span id="node-dialog-library-save-type"></span> called <span id="node-dialog-library-save-name"></span> already exists. Overwrite?
</div>
</form>
</div>
<div id="node-dialog-library-save" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="node-dialog-library-save-folder"><i class="fa fa-folder-open"></i> Folder</label>
<input type="text" id="node-dialog-library-save-folder" placeholder="Folder">
</div>
<div class="form-row">
<label for="node-dialog-library-save-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-dialog-library-save-filename" placeholder="Filename">
</div>
</form>
</div>
<div id="node-dialog-library-lookup" class="hide">
<form class="form-horizontal">
<div class="form-row">
<ul id="node-dialog-library-breadcrumbs" class="breadcrumb">
<li class="active"><a href="#">Library</a></li>
</ul>
</div>
<div class="form-row">
<div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;">
<div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div>
</div>
<div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;">
<div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div>
</div>
</div>
</form>
</div>
<div id="node-dialog-rename-workspace" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="node-input-workspace-name" ><i class="fa fa-tag"></i> Name:</label>
<input type="text" id="node-input-workspace-name">
</div>
</form>
</div>
<div id="node-dialog-delete-workspace" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
Are you sure you want to delete '<span id="node-dialog-delete-workspace-name"></span>'?
</div>
</form>
</div>
<script type="text/x-red" data-template-name="export-library-dialog">
<div class="form-row">
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
</script>
<script type="text/x-red" data-template-name="subflow">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
</div>
</script>
<script src="vendor/vendor.js"></script>
<script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script>
<script src="red/red.min.js"></script>
</body>
</html>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* Copyright 2014, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,23 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.comms = (function() {
var errornotification = null;
var clearErrorTimer = null;
var subscriptions = {};
var ws;
var pendingAuth = false;
function connectWS() {
var path = location.hostname+":"+location.port+document.location.pathname;
var path = location.hostname;
var port = location.port;
if (port.length !== 0) {
path = path+":"+port;
}
path = path+document.location.pathname;
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
var auth_tokens = RED.settings.get("auth-tokens");
pendingAuth = (auth_tokens!=null);
function completeConnection() {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
@@ -37,7 +43,7 @@ RED.comms = (function() {
}
}
}
ws = new WebSocket(path);
ws.onopen = function() {
if (errornotification) {
@@ -75,7 +81,7 @@ RED.comms = (function() {
};
ws.onclose = function() {
if (errornotification == null) {
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true);
errornotification = RED.notify(RED._("notification.error",{message:RED._("notification.errors.lostConnection")}),"error",true);
} else if (clearErrorTimer) {
clearTimeout(clearErrorTimer);
clearErrorTimer = null;
@@ -83,7 +89,7 @@ RED.comms = (function() {
setTimeout(connectWS,1000);
}
}
function subscribe(topic,callback) {
if (subscriptions[topic] == null) {
subscriptions[topic] = [];
@@ -93,7 +99,7 @@ RED.comms = (function() {
ws.send(JSON.stringify({subscribe:topic}));
}
}
function unsubscribe(topic,callback) {
if (subscriptions[topic]) {
for (var i=0;i<subscriptions[topic].length;i++) {
@@ -107,7 +113,7 @@ RED.comms = (function() {
}
}
}
return {
connect: connectWS,
subscribe: subscribe,

48
editor/js/events.js Normal file
View File

@@ -0,0 +1,48 @@
/**
* Copyright 2015 IBM Corp.
*
* 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.
**/
RED.events = (function() {
var handlers = {};
function on(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
}
function off(evt,func) {
var handler = handlers[evt];
if (handler) {
for (var i=0;i<handler.length;i++) {
if (handler[i] === func) {
handler.splice(i,1);
return;
}
}
}
}
function emit(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
return {
on: on,
off: off,
emit: emit
}
})();

43
editor/js/i18n.js Normal file
View File

@@ -0,0 +1,43 @@
/**
* Copyright 2013, 2015 IBM Corp.
*
* 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.
**/
RED.i18n = (function() {
return {
init: function(done) {
i18n.init({
resGetPath: 'locales/__ns__',
dynamicLoad: false,
load:'current',
ns: {
namespaces: ["editor","node-red"],
defaultNs: "editor"
},
fallbackLng: ['en-US'],
useCookie: false
},function() {
done();
});
RED["_"] = function() {
return i18n.t.apply(null,arguments);
}
},
loadCatalog: function(namespace,done) {
i18n.loadNamespace(namespace,done);
}
}
})();

View File

@@ -16,10 +16,6 @@
var RED = (function() {
function loadSettings() {
RED.settings.init(loadNodeList);
}
function loadNodeList() {
$.ajax({
headers: {
@@ -29,7 +25,23 @@ var RED = (function() {
url: 'nodes',
success: function(data) {
RED.nodes.setNodeList(data);
loadNodes();
var nsCount = 0;
for(var i=0;i<data.length;i++) {
var ns = data[i];
if (ns.module != "node-red") {
nsCount++;
RED.i18n.loadCatalog(ns.id, function() {
nsCount--;
if (nsCount === 0) {
loadNodes();
}
});
}
}
if (nsCount === 0) {
loadNodes();
}
}
});
}
@@ -43,6 +55,9 @@ var RED = (function() {
url: 'nodes',
success: function(data) {
$("body").append(data);
$("body").i18n();
$(".palette-spinner").hide();
$(".palette-scroll").show();
$("#palette-search").show();
@@ -66,6 +81,9 @@ var RED = (function() {
var parts = topic.split("/");
var node = RED.nodes.node(parts[1]);
if (node) {
if (msg.text) {
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
}
node.status = msg;
if (statusEnabled) {
node.dirty = true;
@@ -91,7 +109,7 @@ var RED = (function() {
}
if (addedTypes.length) {
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(addedTypes.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
}
} else if (topic == "node/removed") {
for (i=0;i<msg.length;i++) {
@@ -99,7 +117,7 @@ var RED = (function() {
info = RED.nodes.removeNodeSet(m.id);
if (info.added) {
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(m.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success");
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
}
}
} else if (topic == "node/enabled") {
@@ -108,12 +126,12 @@ var RED = (function() {
if (info.added) {
RED.nodes.enableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" enabled:"+typeList,"success");
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
} else {
$.get('nodes/'+msg.id, function(data) {
$("body").append(data);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
});
}
}
@@ -121,7 +139,7 @@ var RED = (function() {
if (msg.types) {
RED.nodes.disableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success");
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
}
}
});
@@ -138,42 +156,43 @@ var RED = (function() {
function loadEditor() {
RED.menu.init({id:"btn-sidemenu",
options: [
{id:"menu-item-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"menu-item-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"menu-item-import",label:"Import",options:[
{id:"menu-item-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
{id:"menu-item-import-library",label:"Library",options:[]}
{id:"menu-item-sidebar-menu",label:RED._("menu.label.sidebar.sidebar"),options:[
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
null
]},
{id:"menu-item-export",label:"Export",disabled:true,options:[
{id:"menu-item-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:"Library",disabled:true,onselect:RED.library.export}
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"menu-item-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]},
{id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
]},
null,
{id:"menu-item-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
null,
{id:"menu-item-subflow",label:"Subflows", options: [
{id:"menu-item-subflow-create",label:"Create subflow",onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"menu-item-workspace",label:"Workspaces",options:[
{id:"menu-item-workspace-add",label:"Add",onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
{id:"menu-item-workspace",label:RED._("menu.label.workspaces"),options:[
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
null
]},
null,
{id:"menu-item-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
{id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp},
{id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
}
]
});
RED.user.init();
RED.library.init();
RED.palette.init();
RED.sidebar.init();
@@ -181,9 +200,10 @@ var RED = (function() {
RED.workspaces.init();
RED.clipboard.init();
RED.view.init();
RED.editor.init();
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
RED.comms.connect();
@@ -194,14 +214,16 @@ var RED = (function() {
}
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = document.title+" : "+window.location.hostname;
}
ace.require("ace/ext/language_tools");
RED.settings.init(loadEditor);
RED.i18n.init(function() {
RED.settings.init(loadEditor);
})
});

View File

@@ -22,20 +22,20 @@ RED.nodes = (function() {
var defaultWorkspace;
var workspaces = {};
var subflows = {};
var dirty = false;
function setDirty(d) {
dirty = d;
eventHandler.emit("change",{dirty:dirty});
RED.events.emit("nodes:change",{dirty:dirty});
}
var registry = (function() {
var nodeList = [];
var nodeSets = {};
var typeToId = {};
var nodeDefinitions = {};
var exports = {
getNodeList: function() {
return nodeList;
@@ -107,7 +107,23 @@ RED.nodes = (function() {
registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def;
if (def.category != "subflows") {
def.set = nodeSets[typeToId[nt]];
nodeSets[typeToId[nt]].added = true;
var ns;
if (def.set.module === "node-red") {
ns = "node-red";
} else {
ns = def.set.id;
}
def["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
if (args[0].indexOf(":") === -1) {
args[0] = ns+":"+args[0];
}
return RED._.apply(null,args);
}
// TODO: too tightly coupled into palette UI
}
RED.palette.add(nt,def);
@@ -117,6 +133,7 @@ RED.nodes = (function() {
},
removeNodeType: function(nt) {
if (nt.substring(0,8) != "subflow:") {
// NON-NLS - internal debug message
throw new Error("this api is subflow only. called with:",nt);
}
delete nodeDefinitions[nt];
@@ -128,12 +145,15 @@ RED.nodes = (function() {
};
return exports;
})();
function getID() {
return (1+Math.random()*4294967295).toString(16);
}
function addNode(n) {
if (n.type.indexOf("subflow") !== 0) {
n["_"] = n._def._;
}
if (n._def.category == "config") {
configNodes[n.id] = n;
RED.sidebar.config.refresh();
@@ -190,6 +210,7 @@ RED.nodes = (function() {
function removeNode(id) {
var removedLinks = [];
var removedNodes = [];
var node;
if (id in configNodes) {
node = configNodes[id];
@@ -211,8 +232,13 @@ RED.nodes = (function() {
var configNode = configNodes[node[d]];
if (configNode) {
updatedConfigNode = true;
var users = configNode.users;
users.splice(users.indexOf(node),1);
if (configNode._def.exclusive) {
removeNode(node[d]);
removedNodes.push(configNode);
} else {
var users = configNode.users;
users.splice(users.indexOf(node),1);
}
}
}
}
@@ -226,7 +252,7 @@ RED.nodes = (function() {
if (node._def.onremove) {
node._def.onremove.call(n);
}
return removedLinks;
return {links:removedLinks,nodes:removedNodes};
}
function removeLink(l) {
@@ -265,7 +291,7 @@ RED.nodes = (function() {
var subflowNames = Object.keys(subflows).map(function(sfid) {
return subflows[sfid].name;
});
subflowNames.sort();
var copyNumber = 1;
var subflowName = sf.name;
@@ -277,7 +303,7 @@ RED.nodes = (function() {
});
sf.name = subflowName;
}
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
@@ -288,10 +314,13 @@ RED.nodes = (function() {
color: "#da9",
label: function() { return this.name||RED.nodes.subflow(sf.id).name },
labelStyle: function() { return this.name?"node_label_italic":""; },
paletteLabel: function() { return RED.nodes.subflow(sf.id).name }
paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
set:{
module: "node-red"
}
});
}
function getSubflow(id) {
return subflows[id];
@@ -300,7 +329,7 @@ RED.nodes = (function() {
delete subflows[sf.id];
registry.removeNodeType("subflow:"+sf.id);
}
function subflowContains(sfid,nodeid) {
for (var i=0;i<nodes.length;i++) {
var node = nodes[i];
@@ -320,7 +349,7 @@ RED.nodes = (function() {
}
return false;
}
function getAllFlowNodes(node) {
var visited = {};
visited[node.id] = true;
@@ -411,7 +440,7 @@ RED.nodes = (function() {
node.name = n.name;
node.in = [];
node.out = [];
n.in.forEach(function(p) {
var nIn = {x:p.x,y:p.y,wires:[]};
var wires = links.filter(function(d) { return d.source === p });
@@ -435,8 +464,8 @@ RED.nodes = (function() {
}
node.out.push(nOut);
});
return node;
}
/**
@@ -516,6 +545,32 @@ RED.nodes = (function() {
return nns;
}
function compareNodes(nodeA,nodeB) {
if (nodeA.id != nodeB.id || nodeA.type != nodeB.type) {
return false;
}
var def = nodeA._def;
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
var vA = nodeA[d];
var vB = nodeB[d];
if (typeof vA !== typeof vB) {
return false;
}
if (vA === null || typeof vA === "string" || typeof vA === "number") {
if (vA !== vB) {
return false;
}
} else {
if (JSON.stringify(vA) !== JSON.stringify(vB)) {
return false;
}
}
}
}
return true;
}
function importNodes(newNodesObj,createNewIds) {
var i;
var n;
@@ -527,7 +582,7 @@ RED.nodes = (function() {
try {
newNodes = JSON.parse(newNodesObj);
} catch(err) {
var e = new Error("Invalid flow: "+err.message);
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
e.code = "NODE_RED";
throw e;
}
@@ -542,21 +597,19 @@ RED.nodes = (function() {
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type != "workspace" &&
n.type != "tab" &&
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) {
unknownTypes.push(n.type);
}
}
if (unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
var type = "type"+(unknownTypes.length > 1?"s":"");
RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
//"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000);
}
var activeWorkspace = RED.workspaces.active();
@@ -568,25 +621,27 @@ RED.nodes = (function() {
var subflowId = m[1];
var err;
if (subflowId === activeSubflow.id) {
err = new Error("Cannot add subflow to itself");
err = new Error(RED._("notification.errors.cannotAddSubflowToItself"));
}
if (subflowContains(m[1],activeSubflow.id)) {
err = new Error("Cannot add subflow - circular reference detected");
err = new Error(RED._("notification.errors.cannotAddCircularReference"));
}
if (err) {
// TODO: standardise error codes
err.code = "NODE_RED";
throw err;
}
}
}
}
var new_workspaces = [];
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var node_map = {};
var new_nodes = [];
var new_links = [];
var nid;
var def;
for (i=0;i<newNodes.length;i++) {
@@ -633,7 +688,8 @@ RED.nodes = (function() {
} else {
def = registry.getNodeType(n.type);
if (def && def.category == "config") {
if (!RED.nodes.node(n.id)) {
var existingConfigNode = RED.nodes.node(n.id);
if (!existingConfigNode || !compareNodes(existingConfigNode,n) || existingConfigNode._def.exclusive) {
var configNode = {id:n.id,type:n.type,users:[]};
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
@@ -642,23 +698,23 @@ RED.nodes = (function() {
}
configNode.label = def.label;
configNode._def = def;
if (existingConfigNode || createNewIds) {
configNode.id = getID();
}
node_map[n.id] = configNode;
RED.nodes.add(configNode);
}
}
}
}
if (defaultWorkspace == null) {
defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
defaultWorkspace = { type:"tab", id:getID(), label:RED._('workspace.defaultName',{number:1})};
addWorkspace(defaultWorkspace);
RED.workspaces.add(defaultWorkspace);
new_workspaces.push(defaultWorkspace);
activeWorkspace = RED.workspaces.active();
}
var node_map = {};
var new_nodes = [];
var new_links = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
@@ -704,11 +760,13 @@ RED.nodes = (function() {
defaults: {},
label: "unknown: "+n.type,
labelStyle: "node_label_italic",
outputs: n.outputs||n.wires.length
outputs: n.outputs||n.wires.length,
set: registry.getNodeSet("node-red/unknown")
}
} else {
node._def = {
category:"config"
category:"config",
set: registry.getNodeSet("node-red/unknown")
};
node.users = [];
}
@@ -727,7 +785,15 @@ RED.nodes = (function() {
node.outputs = n.outputs||node._def.outputs;
for (var d2 in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d2)) {
node[d2] = n[d2];
if (node._def.defaults[d2].type) {
if (node_map[n[d2]]) {
node[d2] = node_map[n[d2]].id;
} else {
node[d2] = n[d2];
}
} else {
node[d2] = n[d2];
}
}
}
}
@@ -779,14 +845,14 @@ RED.nodes = (function() {
delete output.wires;
});
}
return [new_nodes,new_links,new_workspaces,new_subflows];
}
// TODO: supports filter.z|type
function filterNodes(filter) {
var result = [];
for (var n=0;n<nodes.length;n++) {
var node = nodes[n];
if (filter.hasOwnProperty("z") && node.z !== filter.z) {
@@ -801,7 +867,7 @@ RED.nodes = (function() {
}
function filterLinks(filter) {
var result = [];
for (var n=0;n<links.length;n++) {
var link = links[n];
if (filter.source) {
@@ -827,58 +893,36 @@ RED.nodes = (function() {
}
return result;
}
// TODO: DRY
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
return {
on: eventHandler.on,
registry:registry,
setNodeList: registry.setNodeList,
getNodeSet: registry.getNodeSet,
addNodeSet: registry.addNodeSet,
removeNodeSet: registry.removeNodeSet,
enableNodeSet: registry.enableNodeSet,
disableNodeSet: registry.disableNodeSet,
registerType: registry.registerNodeType,
getType: registry.getNodeType,
convertNode: convertNode,
add: addNode,
remove: removeNode,
addLink: addLink,
removeLink: removeLink,
addWorkspace: addWorkspace,
removeWorkspace: removeWorkspace,
workspace: getWorkspace,
addSubflow: addSubflow,
removeSubflow: removeSubflow,
subflow: getSubflow,
subflowContains: subflowContains,
eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) {
cb(nodes[n]);
@@ -903,14 +947,14 @@ RED.nodes = (function() {
}
}
},
node: getNode,
filterNodes: filterNodes,
filterLinks: filterLinks,
import: importNodes,
getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet,

View File

@@ -16,64 +16,87 @@
RED.clipboard = (function() {
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
buttons: [
{
id: "clipboard-dialog-ok",
text: "Ok",
click: function() {
if (/Import/.test(dialog.dialog("option","title"))) {
RED.view.importNodes($("#clipboard-import").val());
}
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-cancel",
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
text: "Close",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
var dialogContainer = dialog.children(".dialog-form");
var exportNodesDialog = '<div class="form-row">'+
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-tips">'+
'Select the text above and copy to the clipboard with Ctrl-C.'+
'</div>';
var importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="Paste nodes here"></textarea>'+
'</div>';
var dialog;
var dialogContainer;
var exportNodesDialog;
var importNodesDialog;
function setupDialogs(){
dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
buttons: [
{
id: "clipboard-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
if (/Import/.test(dialog.dialog("option","title"))) {
RED.view.importNodes($("#clipboard-import").val());
}
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
text: RED._("common.label.close"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
dialogContainer = dialog.children(".dialog-form");
exportNodesDialog = '<div class="form-row">'+
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> '+RED._("clipboard.nodes")+'</label>'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-tips">'+
RED._("clipboard.selectNodes")+
'</div>';
importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
RED._("clipboard.pasteNodes")+
'"></textarea>'+
'</div>';
}
function validateImport() {
var importInput = $("#clipboard-import");
var v = importInput.val();
try {
JSON.parse(v);
importInput.removeClass("input-error");
$("#clipboard-dialog-ok").button("enable");
} catch(err) {
if (v !== "") {
importInput.addClass("input-error");
}
$("#clipboard-dialog-ok").button("disable");
}
}
function importNodes() {
dialogContainer.empty();
dialogContainer.append($(importNodesDialog));
@@ -81,22 +104,12 @@ RED.clipboard = (function() {
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-close").hide();
$("#clipboard-dialog-ok").button("disable");
$("#clipboard-import").keyup(function() {
var v = $(this).val();
try {
JSON.parse(v);
$(this).removeClass("input-error");
$("#clipboard-dialog-ok").button("enable");
} catch(err) {
if (v !== "") {
$(this).addClass("input-error");
}
$("#clipboard-dialog-ok").button("disable");
}
});
dialog.dialog("option","title","Import nodes").dialog("open");
$("#clipboard-import").keyup(validateImport);
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
}
function exportNodes() {
dialogContainer.empty();
dialogContainer.append($(exportNodesDialog));
@@ -116,18 +129,19 @@ RED.clipboard = (function() {
return false;
})
});
dialog.dialog("option","title","Export nodes to clipboard").dialog( "open" );
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
}
}
function hideDropTarget() {
$("#dropTarget").hide();
RED.keyboard.remove(/* ESCAPE */ 27);
}
return {
init: function() {
RED.view.on("selection-changed",function(selection) {
setupDialogs();
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
@@ -140,16 +154,16 @@ RED.clipboard = (function() {
});
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
$('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'});
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
}
});
$('#dropTarget').on("dragover",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
event.preventDefault();
@@ -164,15 +178,15 @@ RED.clipboard = (function() {
RED.view.importNodes(data);
event.preventDefault();
});
},
import: importNodes,
export: exportNodes
}
})();

View File

@@ -13,29 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.deploy = (function() {
var deploymentTypes = {
"full":{img:"red/images/deploy-full-o.png"},
"nodes":{img:"red/images/deploy-nodes-o.png"},
"flows":{img:"red/images/deploy-flows-o.png"}
}
var ignoreDeployWarnings = {
unknown: false,
unusedConfig: false,
invalid: false
}
var deploymentType = "full";
function changeDeploymentType(type) {
deploymentType = type;
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
}
/**
* options:
* type: "default" - Button with drop-down options - no further customisation available
@@ -46,35 +46,35 @@ RED.deploy = (function() {
function init(options) {
options = options || {};
var type = options.type || "default";
if (type == "default") {
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#"><img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> <span>Deploy</span></a>'+
'<a id="btn-deploy" class="deploy-button disabled" href="#"><img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> <span>'+RED._("deploy.deploy")+'</span></a>'+
'<a id="btn-deploy-options" data-toggle="dropdown" class="deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></li>').prependTo(".header-toolbar");
RED.menu.init({id:"btn-deploy-options",
options: [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
]
});
} else if (type == "simple") {
var label = options.label || "Deploy";
var label = options.label || RED._("deploy.deploy");
var icon = 'red/images/deploy-full-o.png';
if (options.hasOwnProperty('icon')) {
icon = options.icon;
}
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
'<span>'+label+'</span></a>'+
'</span></li>').prependTo(".header-toolbar");
}
$('#btn-deploy').click(function() { save(); });
$( "#node-dialog-confirm-deploy" ).dialog({
title: "Confirm deploy",
modal: true,
@@ -83,9 +83,9 @@ RED.deploy = (function() {
height: "auto",
buttons: [
{
text: "Confirm deploy",
text: RED._("deploy.confirm.button.confirm"),
click: function() {
var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked");
if (ignoreChecked) {
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
@@ -95,7 +95,7 @@ RED.deploy = (function() {
}
},
{
text: "Cancel",
text: RED._("deploy.confirm.button.cancel"),
click: function() {
$( this ).dialog( "close" );
}
@@ -111,10 +111,10 @@ RED.deploy = (function() {
}
});
RED.nodes.on('change',function(state) {
RED.events.on('nodes:change',function(state) {
if (state.dirty) {
window.onbeforeunload = function() {
return "You have undeployed changes.\n\nLeaving this page will lose these changes.";
return RED._("deploy.confirm.undeployedChanges");
}
$("#btn-deploy").removeClass("disabled");
} else {
@@ -132,7 +132,7 @@ RED.deploy = (function() {
var hasUnknown = false;
var hasInvalid = false;
var hasUnusedConfig = false;
var unknownNodes = [];
RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid;
@@ -143,7 +143,7 @@ RED.deploy = (function() {
}
});
hasUnknown = unknownNodes.length > 0;
var unusedConfigNodes = {};
RED.nodes.eachConfig(function(node) {
if (node.users.length === 0) {
@@ -159,13 +159,13 @@ RED.deploy = (function() {
hasUnusedConfig = true;
}
});
$( "#node-dialog-confirm-deploy-config" ).hide();
$( "#node-dialog-confirm-deploy-unknown" ).hide();
$( "#node-dialog-confirm-deploy-unused" ).hide();
var showWarning = false;
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("unknown");
@@ -186,7 +186,7 @@ RED.deploy = (function() {
unusedConfigNodes[type].forEach(function(label) {
unusedNodeLabels.push(type+": "+label);
});
});
});
$( "#node-dialog-confirm-deploy-unused-list" )
.html("<li>"+unusedNodeLabels.join("</li><li>")+"</li>");
}
@@ -196,10 +196,10 @@ RED.deploy = (function() {
return;
}
}
var nns = RED.nodes.createCompleteNodeSet();
$("#btn-deploy-icon").removeClass('fa-download');
@@ -215,7 +215,7 @@ RED.deploy = (function() {
"Node-RED-Deployment-Type":deploymentType
}
}).done(function(data,textStatus,xhr) {
RED.notify("Successfully deployed","success");
RED.notify(RED._("deploy.successfulDeploy"),"success");
RED.nodes.eachNode(function(node) {
if (node.changed) {
node.dirty = true;
@@ -233,12 +233,13 @@ RED.deploy = (function() {
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
RED.events.emit("deploy");
}).fail(function(xhr,textStatus,err) {
RED.nodes.dirty(true);
if (xhr.responseText) {
RED.notify("<strong>Error</strong>: "+xhr.responseJSON.message,"error");
RED.notify(RED._("notification.error",{message:xhr.responseText}),"error");
} else {
RED.notify("<strong>Error</strong>: no response from server","error");
RED.notify(RED._("notification.error",{message:RED._("deploy.errors.noResponse")}),"error");
}
}).always(function() {
$("#btn-deploy-icon").removeClass('spinner');

View File

@@ -168,181 +168,210 @@ RED.editor = (function() {
return removedLinks;
}
$( "#dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
minWidth: 500,
width: 'auto',
buttons: [
{
id: "node-dialog-ok",
text: "Ok",
click: function() {
if (editing_node) {
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
function createDialog(){
$( "#dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
minWidth: 500,
width: 'auto',
buttons: [
{
id: "node-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
if (editing_node) {
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
if (editing_node._def.oneditsave) {
var oldValues = {};
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
changed = true;
}
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
if (editing_node._def.oneditsave) {
var oldValues = {};
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
}
}
var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
changed = true;
}
if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d);
var newValue;
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else {
newValue = input.val();
}
if (newValue != null) {
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_node),1);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_node);
}
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
}
}
}
}
}
if (editing_node._def.credentials) {
var prefix = 'node-input';
var credDefinition = editing_node._def.credentials;
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
changed = changed || credsChanged;
}
var removedLinks = updateNodeProperties(editing_node);
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
}
editing_node.dirty = true;
validateNode(editing_node);
RED.view.redraw();
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
//TODO: move this to RED.library
var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify("Saved nodes","success");
});
if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d);
var newValue;
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else {
newValue = input.val();
}
if (newValue != null) {
if (d === "outputs" && (newValue.trim() === "" || isNaN(newValue))) {
continue;
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_node),1);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_node);
}
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
}
}
}
}
}
if (editing_node._def.credentials) {
var prefix = 'node-input';
var credDefinition = editing_node._def.credentials;
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
changed = changed || credsChanged;
}
var removedLinks = updateNodeProperties(editing_node);
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
}
editing_node.dirty = true;
validateNode(editing_node);
RED.view.redraw();
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
//TODO: move this to RED.library
var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify(RED._("library.savedNodes"),"success");
}).fail(function(xhr,textStatus,err) {
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
});
}
}
$( this ).dialog( "close" );
}
$( this ).dialog( "close" );
},
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
if (editing_node && editing_node._def) {
if (editing_node._def.oneditcancel) {
editing_node._def.oneditcancel.call(editing_node);
}
for (var d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var def = editing_node._def.defaults[d];
if (def.type) {
var configTypeDef = RED.nodes.getType(def.type);
if (configTypeDef && configTypeDef.exclusive) {
var input = $("#node-input-"+d).val()||"";
if (input !== "" && !editing_node[d]) {
// This node has an exclusive config node that
// has just been added. As the user is cancelling
// the edit, need to delete the just-added config
// node so that it doesn't get orphaned.
RED.nodes.remove(input);
}
}
}
}
}
}
$( this ).dialog( "close" );
}
}
],
resize: function(e,ui) {
if (editing_node) {
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
}
},
{
id: "node-dialog-cancel",
text: "Cancel",
click: function() {
if (editing_node && editing_node._def) {
if (editing_node._def.oneditcancel) {
editing_node._def.oneditcancel.call(editing_node);
}
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
} else {
$(this).dialog('option','width',$(this).outerWidth());
}
RED.keyboard.disable();
if (editing_node) {
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
if (size) {
$(this).dialog('option','width',size.width);
$(this).dialog('option','height',size.height);
}
$( this ).dialog( "close" );
}
}
],
resize: function(e,ui) {
if (editing_node) {
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
}
},
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
} else {
$(this).dialog('option','width',$(this).outerWidth());
}
RED.keyboard.disable();
if (editing_node) {
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
if (size) {
$(this).dialog('option','width',size.width);
$(this).dialog('option','height',size.height);
},
close: function(e) {
RED.keyboard.enable();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
}
},
close: function(e) {
RED.keyboard.enable();
$( this ).dialog('option','height','auto');
$( this ).dialog('option','width','auto');
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
var buttons = $( this ).dialog("option","buttons");
if (buttons.length == 3) {
$( this ).dialog("option","buttons",buttons.splice(1));
}
editing_node = null;
}
$( this ).dialog('option','height','auto');
$( this ).dialog('option','width','auto');
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
var buttons = $( this ).dialog("option","buttons");
if (buttons.length == 3) {
$( this ).dialog("option","buttons",buttons.splice(1));
}
editing_node = null;
}
});
});
}
/**
* Create a config-node select box for this property
@@ -357,7 +386,7 @@ RED.editor = (function() {
input.replaceWith('<select style="width: 60%;" id="node-input-'+property+'"></select>');
updateConfigNodeSelect(property,type,node[property]);
var select = $("#node-input-"+property);
select.after(' <a id="node-input-lookup-'+property+'" class="btn"><i class="fa fa-pencil"></i></a>');
select.after(' <a id="node-input-lookup-'+property+'" class="editor-button"><i class="fa fa-pencil"></i></a>');
$('#node-input-lookup-'+property).click(function(e) {
showEditConfigNodeDialog(property,type,select.find(":selected").val());
e.preventDefault();
@@ -385,13 +414,13 @@ RED.editor = (function() {
input.val(node[property]);
input.attr("type","hidden");
var button = $("<a>",{id:"node-input-edit-"+property, class:"btn"});
var button = $("<a>",{id:"node-input-edit-"+property, class:"editor-button"});
input.after(button);
if (node[property]) {
button.text("edit");
button.text(RED._("editor.configEdit"));
} else {
button.text("add");
button.text(RED._("editor.configAdd"));
}
button.click(function(e) {
@@ -513,7 +542,8 @@ RED.editor = (function() {
for (var d in definition.defaults) {
if (definition.defaults.hasOwnProperty(d)) {
if (definition.defaults[d].type) {
if (definition.defaults[d].exclusive) {
var configTypeDef = RED.nodes.getType(definition.defaults[d].type);
if (configTypeDef && configTypeDef.exclusive) {
prepareConfigNodeButton(node,d,definition.defaults[d].type);
} else {
prepareConfigNodeSelect(node,d,definition.defaults[d].type);
@@ -562,7 +592,7 @@ RED.editor = (function() {
var buttons = $( "#dialog" ).dialog("option","buttons");
buttons.unshift({
class: 'leftButton',
text: "Edit flow",
text: RED._("subflow.edit"),
click: function() {
RED.workspaces.show(id);
$("#node-dialog-ok").click();
@@ -571,21 +601,44 @@ RED.editor = (function() {
$( "#dialog" ).dialog("option","buttons",buttons);
}
$("#dialog-form").html($("script[data-template-name='"+type+"']").html());
var ns;
if (node._def.set.module === "node-red") {
ns = "node-red";
} else {
ns = node._def.set.id;
}
$("#dialog-form").find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
});
$('<input type="text" style="display: none;" />').appendTo("#dialog-form");
prepareEditDialog(node,node._def,"node-input");
$("#dialog").i18n();
$( "#dialog" ).dialog("option","title","Edit "+type+" node").dialog( "open" );
}
function showEditConfigNodeDialog(name,type,id) {
var adding = (id == "_ADD_");
var node_def = RED.nodes.getType(type);
var configNode = RED.nodes.node(id);
var ns;
if (node_def.set.module === "node-red") {
ns = "node-red";
} else {
ns = node_def.set.id;
}
if (configNode == null) {
configNode = {
id: (1+Math.random()*4294967295).toString(16),
@@ -597,9 +650,25 @@ RED.editor = (function() {
configNode[d] = node_def.defaults[d].value;
}
}
configNode["_"] = node_def._;
}
$("#dialog-config-form").html($("script[data-template-name='"+type+"']").html());
$("#dialog-config-form").find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
});
prepareEditDialog(configNode,node_def,"node-config-input");
var buttons = $( "#node-config-dialog" ).dialog("option","buttons");
@@ -613,7 +682,7 @@ RED.editor = (function() {
if (buttons.length == 2) {
buttons.unshift({
class: 'leftButton',
text: "Delete",
text: RED._("editor.configDelete"),
click: function() {
var configProperty = $(this).dialog('option','node-property');
var configId = $(this).dialog('option','node-id');
@@ -645,16 +714,18 @@ RED.editor = (function() {
});
}
buttons[1].text = "Update";
$("#node-config-dialog-user-count").html(configNode.users.length+" node"+(configNode.users.length==1?" uses":"s use")+" this config").show();
$("#node-config-dialog-user-count").html(RED._("editor.nodesUse", {count:configNode.users.length})).show();
}
$( "#node-config-dialog" ).dialog("option","buttons",buttons);
$("#node-config-dialog").i18n();
$( "#node-config-dialog" )
.dialog("option","node-adding",adding)
.dialog("option","node-property",name)
.dialog("option","node-id",configNode.id)
.dialog("option","node-type",type)
.dialog("option","title",(adding?"Add new ":"Edit ")+type+" config node")
.dialog("option","title",(adding?RED._("editor.addNewConfig", {type:type}):RED._("editor.editConfig", {type:type})))
.dialog( "open" );
}
@@ -662,9 +733,9 @@ RED.editor = (function() {
var button = $("#node-input-edit-"+name);
if (button.length) {
if (value) {
button.text("edit");
button.text(RED._("editor.configEdit"));
} else {
button.text("add");
button.text(RED._("editor.configAdd"));
}
$("#node-input-"+name).val(value);
} else {
@@ -683,208 +754,211 @@ RED.editor = (function() {
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
}
});
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>');
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>');
window.setTimeout(function() { select.change();},50);
}
}
$( "#node-config-dialog" ).dialog({
function createNodeConfigDialog(){
$( "#node-config-dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
minWidth: 500,
width: 'auto',
closeOnEscape: false,
buttons: [
{
id: "node-config-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
var configProperty = $(this).dialog('option','node-property');
var configId = $(this).dialog('option','node-id');
var configType = $(this).dialog('option','node-type');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
var configNode;
var d;
var input;
if (configAdding) {
configNode = {type:configType,id:configId,users:[]};
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
configNode.label = configTypeDef.label;
configNode._def = configTypeDef;
RED.nodes.add(configNode);
updateConfigNodeSelect(configProperty,configType,configNode.id);
} else {
configNode = RED.nodes.node(configId);
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
updateConfigNodeSelect(configProperty,configType,configId);
}
if (configTypeDef.credentials) {
updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input");
}
if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(RED.nodes.node(configId));
}
validateNode(configNode);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
validateNode(user);
}
RED.nodes.dirty(true);
$(this).dialog("close");
}
},
{
id: "node-config-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
var configType = $(this).dialog('option','node-type');
var configId = $(this).dialog('option','node-id');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.oneditcancel) {
// TODO: what to pass as this to call
if (configTypeDef.oneditcancel) {
var cn = RED.nodes.node(configId);
if (cn) {
configTypeDef.oneditcancel.call(cn,false);
} else {
configTypeDef.oneditcancel.call({id:configId},true);
}
}
}
$( this ).dialog( "close" );
}
}
],
resize: function(e,ui) {
},
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.disable();
}
},
close: function(e) {
$(this).dialog('option','width','auto');
$(this).dialog('option','height','auto');
$("#dialog-config-form").html("");
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.enable();
}
RED.sidebar.config.refresh();
}
});
}
function createSubflowDialog(){
$( "#subflow-dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
minWidth: 500,
width: 'auto',
closeOnEscape: false,
buttons: [
{
id: "node-config-dialog-ok",
text: "Ok",
id: "subflow-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
var configProperty = $(this).dialog('option','node-property');
var configId = $(this).dialog('option','node-id');
var configType = $(this).dialog('option','node-type');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
var configNode;
var d;
var input;
if (editing_node) {
var i;
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
if (configAdding) {
configNode = {type:configType,id:configId,users:[]};
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
var newName = $("#subflow-input-name").val();
if (newName != editing_node.name) {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(RED._("subflow.tabLabel",{name:newName}));
}
RED.palette.refresh();
if (changed) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
n.changed = true;
updateNodeProperties(n);
}
}
}
configNode.label = configTypeDef.label;
configNode._def = configTypeDef;
RED.nodes.add(configNode);
updateConfigNodeSelect(configProperty,configType,configNode.id);
} else {
configNode = RED.nodes.node(configId);
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
updateConfigNodeSelect(configProperty,configType,configId);
}
if (configTypeDef.credentials) {
updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input");
}
if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(RED.nodes.node(configId));
}
validateNode(configNode);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
validateNode(user);
}
});
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.nodes.dirty(true);
$(this).dialog("close");
}
},
{
id: "node-config-dialog-cancel",
text: "Cancel",
click: function() {
var configType = $(this).dialog('option','node-type');
var configId = $(this).dialog('option','node-id');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.oneditcancel) {
// TODO: what to pass as this to call
if (configTypeDef.oneditcancel) {
var cn = RED.nodes.node(configId);
if (cn) {
configTypeDef.oneditcancel.call(cn,false);
} else {
configTypeDef.oneditcancel.call({id:configId},true);
}
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.view.redraw();
}
$( this ).dialog( "close" );
}
},
{
id: "subflow-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
editing_node = null;
}
}
],
resize: function(e,ui) {
},
open: function(e) {
RED.keyboard.disable();
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.disable();
}
},
close: function(e) {
$(this).dialog('option','width','auto');
$(this).dialog('option','height','auto');
$("#dialog-config-form").html("");
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.enable();
RED.keyboard.enable();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
RED.sidebar.config.refresh();
RED.sidebar.info.refresh(editing_node);
editing_node = null;
}
});
$( "#subflow-dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
minWidth: 500,
width: 'auto',
buttons: [
{
id: "subflow-dialog-ok",
text: "Ok",
click: function() {
if (editing_node) {
var i;
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var newName = $("#subflow-input-name").val();
if (newName != editing_node.name) {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
}
RED.palette.refresh();
if (changed) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
n.changed = true;
updateNodeProperties(n);
}
});
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.view.redraw();
}
$( this ).dialog( "close" );
}
},
{
id: "subflow-dialog-cancel",
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
editing_node = null;
}
}
],
open: function(e) {
RED.keyboard.disable();
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
},
close: function(e) {
RED.keyboard.enable();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
RED.sidebar.info.refresh(editing_node);
editing_node = null;
}
});
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
});
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
}
function showEditSubflowDialog(subflow) {
@@ -900,13 +974,18 @@ RED.editor = (function() {
}
});
$("#subflow-dialog-user-count").html("There "+(userCount==1?"is":"are")+" "+userCount+" instance"+(userCount==1?" ":"s")+" of this subflow").show();
$("#subflow-dialog").dialog("option","title","Edit flow "+subflow.name).dialog( "open" );
$("#subflow-dialog-user-count").html(RED._("subflow.subflowInstances", {count:userCount})).show();
$("#subflow-dialog").dialog("option","title",RED._("subflow.editSubflow",{name:subflow.name})).dialog( "open" );
}
return {
init: function(){
createDialog();
createNodeConfigDialog();
createSubflowDialog();
},
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,

View File

@@ -69,24 +69,24 @@ RED.keyboard = (function() {
dialog = $('<div id="keyboard-help-dialog" class="hide">'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>'+RED._("keyboard.selectAll")+'</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.selectAllConnected")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.addRemoveNode")+'</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteSelected")+'</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>'+RED._("keyboard.importNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>'+RED._("keyboard.exportNode")+'</td></tr>'+
'</table>'+
'</div>'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteNode")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>'+RED._("keyboard.copyNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>'+RED._("keyboard.cutNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>'+RED._("keyboard.pasteNode")+'</td></tr>'+
'</table>'+
'</div>'+
'</div>')

View File

@@ -14,8 +14,8 @@
* limitations under the License.
**/
RED.library = (function() {
function loadFlowLibrary() {
$.getJSON("library/flows",function(data) {
//console.log(data);
@@ -66,12 +66,12 @@ RED.library = (function() {
$("#menu-item-import-library-submenu").replaceWith(menu);
});
}
function createUI(options) {
var libraryData = {};
var selectedLibraryItem = null;
var libraryEditor = null;
// Orion editor has set/getText
// ACE editor has set/getValue
// normalise to set/getValue
@@ -84,14 +84,14 @@ RED.library = (function() {
if (options.editor.getText) {
options.editor.getValue = options.editor.getText;
}
function buildFileListItem(item) {
var li = document.createElement("li");
li.onmouseover = function(e) { $(this).addClass("list-hover"); };
li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
return li;
}
function buildFileList(root,data) {
var ul = document.createElement("ul");
var li;
@@ -104,7 +104,7 @@ RED.library = (function() {
var dirName = v;
return function(e) {
var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
$("a",bcli).click(function(e) {
$("a",bcli).click(function(e) {
$(this).parent().nextAll().remove();
$.getJSON("library/"+options.url+root+dirName,function(data) {
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
@@ -141,24 +141,24 @@ RED.library = (function() {
}
return ul;
}
$('#node-input-name').addClass('input-append-left').css("width","65%").after(
'<div class="btn-group" style="margin-left: 0px;">'+
'<button id="node-input-'+options.type+'-lookup" class="btn input-append-right" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></button>'+
$('#node-input-name').css("width","60%").after(
'<div class="btn-group" style="margin-left: 5px;">'+
'<a id="node-input-'+options.type+'-lookup" class="editor-button" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></a>'+
'<ul class="dropdown-menu pull-right" role="menu">'+
'<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">Open Library...</a></li>'+
'<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">Save to Library...</a></li>'+
'<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">'+RED._("library.openLibrary")+'</a></li>'+
'<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">'+RED._("library.saveToLibrary")+'</a></li>'+
'</ul></div>'
);
$('#node-input-'+options.type+'-menu-open-library').click(function(e) {
$("#node-select-library").children().remove();
var bc = $("#node-dialog-library-breadcrumbs");
bc.children().first().nextAll().remove();
libraryEditor.setValue('',-1);
$.getJSON("library/"+options.url,function(data) {
$("#node-select-library").append(buildFileList("/",data));
$("#node-dialog-library-breadcrumbs a").click(function(e) {
@@ -168,10 +168,10 @@ RED.library = (function() {
});
$( "#node-dialog-library-lookup" ).dialog( "open" );
});
e.preventDefault();
});
$('#node-input-'+options.type+'-menu-save-library').click(function(e) {
//var found = false;
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
@@ -217,7 +217,7 @@ RED.library = (function() {
$( "#node-dialog-library-save" ).dialog( "open" );
e.preventDefault();
});
libraryEditor = ace.edit('node-select-library-text');
libraryEditor.setTheme("ace/theme/tomorrow");
if (options.mode) {
@@ -230,16 +230,16 @@ RED.library = (function() {
});
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
libraryEditor.$blockScrolling = Infinity;
$( "#node-dialog-library-lookup" ).dialog({
title: options.type+" library",
title: RED._("library.typeLibrary", {type:options.type}),
modal: true,
autoOpen: false,
width: 800,
height: 450,
buttons: [
{
text: "Ok",
text: RED._("common.label.ok"),
click: function() {
if (selectedLibraryItem) {
for (var i=0;i<options.fields.length;i++) {
@@ -252,7 +252,7 @@ RED.library = (function() {
}
},
{
text: "Cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
@@ -270,16 +270,16 @@ RED.library = (function() {
$(".form-row:last-child",form).children().height(form.height()-60);
}
});
function saveToLibrary(overwrite) {
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
if (name === "") {
name = "Unnamed "+options.type;
name = RED._("library.unnamedType",{type:options.type});
}
var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,"");
var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
if (filename === "" || !/.+\.js$/.test(filename)) {
RED.notify("Invalid filename","warning");
RED.notify(RED._("library.invalidFilename"),"warning");
return;
}
var fullpath = pathname+(pathname===""?"":"/")+filename;
@@ -304,8 +304,7 @@ RED.library = (function() {
// }
//}
//if (exists) {
// $("#node-dialog-library-save-type").html(options.type);
// $("#node-dialog-library-save-name").html(fullpath);
// $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
// $("#node-dialog-library-save-confirm").dialog( "open" );
// return;
//}
@@ -320,7 +319,7 @@ RED.library = (function() {
data[field] = $("#node-input-"+field).val();
}
}
data.text = options.editor.getValue();
$.ajax({
url:"library/"+options.url+'/'+fullpath,
@@ -328,27 +327,27 @@ RED.library = (function() {
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
RED.notify("Saved "+options.type,"success");
RED.notify(RED._("library.savedType", {type:options.type}),"success");
}).fail(function(xhr,textStatus,err) {
RED.notify("Saved failed: "+xhr.responseJSON.message,"error");
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
});
}
$( "#node-dialog-library-save-confirm" ).dialog({
title: "Save to library",
title: RED._("library.saveToLibrary"),
modal: true,
autoOpen: false,
width: 530,
height: 230,
buttons: [
{
text: "Ok",
text: RED._("common.label.ok"),
click: function() {
saveToLibrary(true);
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
@@ -356,21 +355,21 @@ RED.library = (function() {
]
});
$( "#node-dialog-library-save" ).dialog({
title: "Save to library",
title: RED._("library.saveToLibrary"),
modal: true,
autoOpen: false,
width: 530,
height: 230,
buttons: [
{
text: "Ok",
text: RED._("common.label.ok"),
click: function() {
saveToLibrary(false);
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
@@ -379,18 +378,19 @@ RED.library = (function() {
});
}
function exportFlow() {
//TODO: don't rely on the main dialog
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
$("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
$("#node-input-filename").attr('nodes',JSON.stringify(nns));
$( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" );
$("#dialog").i18n();
$("#dialog").dialog("option","title",RED._("library.exportToLibrary")).dialog( "open" );
}
return {
init: function() {
RED.view.on("selection-changed",function(selection) {
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
@@ -401,16 +401,14 @@ RED.library = (function() {
RED.menu.setDisabled("menu-item-export-library",false);
}
});
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary();
}
},
create: createUI,
loadFlowLibrary: loadFlowLibrary,
export: exportFlow
}
})();

View File

@@ -29,7 +29,7 @@ RED.menu = (function() {
return null;
}
}
function setInitialState() {
var savedStateActive = isSavedStateActive(opt.id);
if (savedStateActive) {
@@ -52,12 +52,16 @@ RED.menu = (function() {
item = $('<li class="divider"></li>');
} else {
item = $('<li></li>');
if (opt.group) {
item.addClass("menu-group-"+opt.group);
}
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
if (opt.toggle) {
linkContent += '<i class="fa fa-square pull-left"></i>';
linkContent += '<i class="fa fa-check-square pull-left"></i>';
}
if (opt.icon !== undefined) {
if (/\.png/.test(opt.icon)) {
@@ -66,16 +70,16 @@ RED.menu = (function() {
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
}
}
if (opt.sublabel) {
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
} else {
linkContent += '<span class="menu-label">'+opt.label+'</span>'
}
linkContent += '</a>';
var link = $(linkContent).appendTo(item);
menuItems[opt.id] = opt;
@@ -129,17 +133,6 @@ RED.menu = (function() {
if (opt.disabled) {
item.addClass("disabled");
}
if (opt.tip) {
item.popover({
placement:"left",
trigger: "hover",
delay: { show: 350, hide: 20 },
html: true,
container:'body',
content: opt.tip
});
}
}
@@ -154,8 +147,8 @@ RED.menu = (function() {
// $("#"+options.id+"-submenu").show();
// event.preventDefault();
//});
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
var lastAddedSeparator = false;
@@ -208,7 +201,27 @@ RED.menu = (function() {
}
function addItem(id,opt) {
createMenuItem(opt).appendTo("#"+id+"-submenu");
var item = createMenuItem(opt);
if (opt.group) {
var groupItems = $("#"+id+"-submenu").children(".menu-group-"+opt.group);
if (groupItems.length === 0) {
item.appendTo("#"+id+"-submenu");
} else {
for (var i=0;i<groupItems.length;i++) {
var groupItem = groupItems[i];
var label = $(groupItem).find(".menu-label").html();
if (opt.label < label) {
$(groupItem).before(item);
break;
}
}
if (i === groupItems.length) {
item.appendTo("#"+id+"-submenu");
}
}
} else {
item.appendTo("#"+id+"-submenu");
}
}
function removeItem(id) {
$("#"+id).parent().remove();

View File

@@ -30,10 +30,10 @@ RED.notify = (function() {
}
var n = document.createElement("div");
n.id="red-notification-"+c;
n.className = "alert";
n.className = "notification";
n.fixed = fixed;
if (type) {
n.className = "alert alert-"+type;
n.className = "notification notification-"+type;
}
n.style.display = "none";
n.innerHTML = msg;
@@ -44,7 +44,7 @@ RED.notify = (function() {
return function() {
currentNotifications.splice(currentNotifications.indexOf(nn),1);
$(nn).slideUp(300, function() {
nn.parentNode.removeChild(nn);
nn.parentNode.removeChild(nn);
});
};
})();
@@ -56,4 +56,3 @@ RED.notify = (function() {
return n;
}
})();

View File

@@ -17,22 +17,46 @@
RED.palette = (function() {
var exclusion = ['config','unknown','deprecated'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
function createCategoryContainer(category){
var escapedCategory = category.replace(" ","_");
var catDiv = $("#palette-container").append('<div id="palette-container-'+category+'" class="palette-category hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-caret-down"></i><span>'+category.replace("_"," ")+'</span></div>'+
var categoryContainers = {};
function createCategoryContainer(category, label){
label = label || category.replace("_", " ");
var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
'<div class="palette-content" id="palette-base-category-'+category+'">'+
'<div id="palette-'+category+'-input"></div>'+
'<div id="palette-'+category+'-output"></div>'+
'<div id="palette-'+category+'-function"></div>'+
'</div>'+
'</div>');
'</div>').appendTo("#palette-container");
categoryContainers[category] = {
container: catDiv,
close: function() {
catDiv.removeClass("palette-open");
catDiv.addClass("palette-closed");
$("#palette-base-category-"+category).slideUp();
$("#palette-header-"+category+" i").removeClass("expanded");
},
open: function() {
catDiv.addClass("palette-open");
catDiv.removeClass("palette-closed");
$("#palette-base-category-"+category).slideDown();
$("#palette-header-"+category+" i").addClass("expanded");
},
toggle: function() {
if (catDiv.hasClass("palette-open")) {
categoryContainers[category].close();
} else {
categoryContainers[category].open();
}
}
};
$("#palette-header-"+category).on('click', function(e) {
$(this).next().slideToggle();
$(this).children("i").toggleClass("expanded");
categoryContainers[category].toggle();
});
}
@@ -77,20 +101,21 @@ RED.palette = (function() {
if (label != type) {
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
}
popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>no information available</p>").trim())
popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
.filter(function(n) {
return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2);
} catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break
console.log("Error generating pop-over label for '"+type+"'.");
// NON-NLS: internal debug
console.log("Error generating pop-over label for ",type);
console.log(err.toString());
popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>";
popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
}
el.data('popover').options.content = popOverContent;
el.data('popover').setContent(popOverContent);
}
function escapeNodeType(nt) {
@@ -120,12 +145,12 @@ RED.palette = (function() {
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
}
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
d.className="palette_node";
if (def.icon) {
var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
@@ -147,7 +172,12 @@ RED.palette = (function() {
}
if ($("#palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(rootCategory);
if(core.indexOf(rootCategory) !== -1){
createCategoryContainer(rootCategory, RED._("node-red:palette.label."+rootCategory, {defaultValue:rootCategory}));
} else {
var ns = def.set.id;
createCategoryContainer(rootCategory, RED._(ns+":palette.label."+rootCategory, {defaultValue:rootCategory}));
}
}
$("#palette-container-"+rootCategory).show();
@@ -158,18 +188,24 @@ RED.palette = (function() {
$("#palette-"+category).append(d);
d.onmousedown = function(e) { e.preventDefault(); };
$(d).popover({
title:d.type,
placement:"right",
trigger: "hover",
delay: { show: 750, hide: 50 },
html: true,
container:'body'
RED.popover.create({
target:$(d),
content: "hi",
delay: { show: 750, hide: 50 }
});
// $(d).popover({
// title:d.type,
// placement:"right",
// trigger: "hover",
// delay: { show: 750, hide: 50 },
// html: true,
// container:'body'
// });
$(d).click(function() {
RED.view.focus();
var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>";
$("#tab-info").html(help);
RED.sidebar.info.set(help);
});
$(d).draggable({
helper: 'clone',
@@ -178,7 +214,7 @@ RED.palette = (function() {
revertDuration: 50,
start: function() {RED.view.focus();}
});
if (def.category == "subflows") {
$(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8));
@@ -187,15 +223,12 @@ RED.palette = (function() {
}
setLabel(nt,$(d),label);
var categoryNode = $("#palette-container-"+category);
if (categoryNode.find(".palette_node").length === 1) {
if (!categoryNode.find("i").hasClass("expanded")) {
categoryNode.find(".palette-content").slideToggle();
categoryNode.find("i").toggleClass("expanded");
}
categoryContainers[category].open();
}
}
}
@@ -263,40 +296,73 @@ RED.palette = (function() {
$(this).hide();
}
});
for (var category in categoryContainers) {
if (categoryContainers.hasOwnProperty(category)) {
if (categoryContainers[category].container
.find(".palette_node")
.filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
categoryContainers[category].close();
} else {
categoryContainers[category].open();
}
}
}
}
function init() {
$(".palette-spinner").show();
if (RED.settings.paletteCategories) {
RED.settings.paletteCategories.forEach(createCategoryContainer);
RED.settings.paletteCategories.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
} else {
core.forEach(createCategoryContainer);
core.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
}
$("#palette-search-input").focus(function(e) {
RED.keyboard.disable();
});
$("#palette-search-input").blur(function(e) {
RED.keyboard.enable();
});
$("#palette-search-clear").on("click",function(e) {
e.preventDefault();
$("#palette-search-input").val("");
filterChange();
$("#palette-search-input").focus();
});
$("#palette-search-input").val("");
$("#palette-search-input").on("keyup",function() {
filterChange();
});
$("#palette-search-input").on("focus",function() {
$("body").one("mousedown",function() {
$("#palette-search-input").blur();
});
});
$("#palette-collapse-all").on("click", function(e) {
e.preventDefault();
for (var cat in categoryContainers) {
if (categoryContainers.hasOwnProperty(cat)) {
categoryContainers[cat].close();
}
}
});
$("#palette-expand-all").on("click", function(e) {
e.preventDefault();
for (var cat in categoryContainers) {
if (categoryContainers.hasOwnProperty(cat)) {
categoryContainers[cat].open();
}
}
});
}
return {

79
editor/js/ui/popover.js Normal file
View File

@@ -0,0 +1,79 @@
/**
* Copyright 2015 IBM Corp.
*
* 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.
**/
RED.popover = (function() {
function createPopover(options) {
var target = options.target;
var content = options.content;
var delay = options.delay;
var timer = null;
var active;
var div;
var openPopup = function() {
if (active) {
div = $('<div class="red-ui-popover"></div>').html(content).appendTo("body");
var targetPos = target.offset();
var targetWidth = target.width();
var targetHeight = target.height();
var divHeight = div.height();
div.css({top: targetPos.top+targetHeight/2-divHeight/2-10,left:targetPos.left+targetWidth+17});
div.fadeIn("fast");
}
}
var closePopup = function() {
if (!active) {
if (div) {
div.fadeOut("fast",function() {
$(this).remove();
});
div = null;
}
}
}
target.on('mouseenter',function(e) {
clearTimeout(timer);
active = true;
timer = setTimeout(openPopup,delay.show);
});
target.on('mouseleave', function(e) {
if (timer) {
clearTimeout(timer);
}
active = false;
setTimeout(closePopup,delay.hide);
});
var res = {
setContent: function(_content) {
content = _content;
}
}
target.data('popover',res);
return res;
}
return {
create: createPopover
}
})();

View File

@@ -20,25 +20,69 @@ RED.sidebar = (function() {
id:"sidebar-tabs",
onchange:function(tab) {
$("#sidebar-content").children().hide();
$("#"+tab.id).show();
if (tab.onchange) {
tab.onchange.call(tab);
}
$(tab.content).show();
},
onremove: function(tab) {
$("#"+tab.id).remove();
}
$(tab.content).hide();
if (tab.onremove) {
tab.onremove.call(tab);
}
},
minimumActiveTabWidth: 110
});
function addTab(title,content,closeable) {
$("#sidebar-content").append(content);
$(content).hide();
sidebar_tabs.addTab({id:"tab-"+title,label:title,closeable:closeable});
//content.style.position = "absolute";
//$('#sidebar').tabs("refresh");
var knownTabs = {
};
function addTab(title,content,closeable,visible) {
var options;
if (typeof title === "string") {
// TODO: legacy support in case anyone uses this...
options = {
id: content.id,
label: title,
name: title,
content: content,
closeable: closeable,
visible: visible
}
} else if (typeof title === "object") {
options = title;
}
$("#sidebar-content").append(options.content);
$(options.content).hide();
var id = options.id;
RED.menu.addItem("menu-item-sidebar-menu",{
id:"menu-item-sidebar-menu-"+options.id,
label:options.name,
onselect:function() {
showSidebar(options.id);
},
group: "sidebar-tabs"
});
knownTabs[options.id] = options;
if (options.visible !== false) {
sidebar_tabs.addTab(knownTabs[options.id]);
}
}
function removeTab(title) {
sidebar_tabs.removeTab("tab-"+title);
function removeTab(id) {
sidebar_tabs.removeTab(id);
$(knownTabs[id].content).remove();
delete knownTabs[id];
RED.menu.removeItem("menu-item-sidebar-menu-"+id);
}
var sidebarSeparator = {};
$("#sidebar-separator").draggable({
axis: "x",
@@ -53,13 +97,12 @@ RED.sidebar = (function() {
if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true;
var newChartRight = 15;
var newChartRight = 7;
$("#sidebar").addClass("closing");
$("#workspace").css("right",newChartRight);
$("#chart-zoom-controls").css("right",newChartRight+20);
$("#sidebar").width(0);
RED.menu.setSelected("menu-item-sidebar",true);
eventHandler.emit("resize");
RED.events.emit("sidebar:resize");
}
sidebarSeparator.width = $("#sidebar").width();
},
@@ -67,9 +110,9 @@ RED.sidebar = (function() {
var d = ui.position.left-sidebarSeparator.start;
var newSidebarWidth = sidebarSeparator.width-d;
if (sidebarSeparator.opening) {
newSidebarWidth -= 13;
newSidebarWidth -= 3;
}
if (newSidebarWidth > 150) {
if (sidebarSeparator.chartWidth+d < 200) {
ui.position.left = 200+sidebarSeparator.start-sidebarSeparator.chartWidth;
@@ -77,7 +120,7 @@ RED.sidebar = (function() {
newSidebarWidth = sidebarSeparator.width-d;
}
}
if (newSidebarWidth < 150) {
if (!sidebarSeparator.closing) {
$("#sidebar").addClass("closing");
@@ -95,11 +138,10 @@ RED.sidebar = (function() {
var newChartRight = sidebarSeparator.chartRight-d;
$("#workspace").css("right",newChartRight);
$("#chart-zoom-controls").css("right",newChartRight+20);
$("#sidebar").width(newSidebarWidth);
sidebar_tabs.resize();
eventHandler.emit("resize");
RED.events.emit("sidebar:resize");
},
stop:function(event,ui) {
if (sidebarSeparator.closing) {
@@ -107,16 +149,15 @@ RED.sidebar = (function() {
RED.menu.setSelected("menu-item-sidebar",false);
if ($("#sidebar").width() < 180) {
$("#sidebar").width(180);
$("#workspace").css("right",208);
$("#chart-zoom-controls").css("right",228);
$("#workspace").css("right",187);
}
}
$("#sidebar-separator").css("left","auto");
$("#sidebar-separator").css("right",($("#sidebar").width()+13)+"px");
eventHandler.emit("resize");
$("#sidebar-separator").css("right",($("#sidebar").width()+2)+"px");
RED.events.emit("sidebar:resize");
}
});
function toggleSidebar(state) {
if (!state) {
$("#main-container").addClass("sidebar-closed");
@@ -124,46 +165,34 @@ RED.sidebar = (function() {
$("#main-container").removeClass("sidebar-closed");
sidebar_tabs.resize();
}
eventHandler.emit("resize");
RED.events.emit("sidebar:resize");
}
function showSidebar(id) {
if (id) {
sidebar_tabs.activateTab("tab-"+id);
if (!containsTab(id)) {
sidebar_tabs.addTab(knownTabs[id]);
}
sidebar_tabs.activateTab(id);
if (!RED.menu.isSelected("menu-item-sidebar")) {
RED.menu.setSelected("menu-item-sidebar",true);
}
}
}
function containsTab(id) {
return sidebar_tabs.contains("tab-"+id);
return sidebar_tabs.contains(id);
}
function init () {
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("menu-item-sidebar",!RED.menu.isSelected("menu-item-sidebar"));d3.event.preventDefault();});
showSidebar();
RED.sidebar.info.show();
RED.sidebar.info.init();
RED.sidebar.config.init();
// hide info bar at start if screen rather narrow...
if ($(window).width() < 600) { toggleSidebar(); }
}
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
return {
init: init,
addTab: addTab,
@@ -171,7 +200,6 @@ RED.sidebar = (function() {
show: showSidebar,
containsTab: containsTab,
toggleSidebar: toggleSidebar,
on: eventHandler.on
}
})();

View File

@@ -15,12 +15,12 @@
**/
RED.subflow = (function() {
function getSubflow() {
return RED.nodes.subflow(RED.workspaces.active());
}
function findAvailableSubflowIOPosition(subflow) {
var pos = {x:70,y:70};
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
@@ -37,7 +37,7 @@ RED.subflow = (function() {
}
return pos;
}
function addSubflowInput() {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow);
@@ -56,7 +56,7 @@ RED.subflow = (function() {
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
@@ -77,11 +77,11 @@ RED.subflow = (function() {
$("#workspace-subflow-add-input").toggleClass("disabled",true);
RED.view.select();
}
function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow);
var newOutput = {
type:"subflow",
direction:"out",
@@ -97,7 +97,7 @@ RED.subflow = (function() {
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
@@ -117,7 +117,7 @@ RED.subflow = (function() {
RED.history.push(historyEvent);
RED.view.select();
}
function init() {
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
@@ -137,13 +137,13 @@ RED.subflow = (function() {
}
addSubflowOutput();
});
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
var removedNodes = [];
var removedLinks = [];
var startDirty = RED.nodes.dirty();
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+getSubflow().id) {
removedNodes.push(n);
@@ -152,16 +152,20 @@ RED.subflow = (function() {
removedNodes.push(n);
}
});
var removedConfigNodes = [];
for (var i=0;i<removedNodes.length;i++) {
var rmlinks = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(rmlinks);
var removedEntities = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(removedEntities.links);
removedConfigNodes = removedConfigNodes.concat(removedEntities.nodes);
}
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
var activeSubflow = getSubflow();
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
@@ -169,22 +173,22 @@ RED.subflow = (function() {
subflow: activeSubflow,
dirty:startDirty
});
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
RED.view.redraw();
});
RED.view.on("selection-changed",function(selection) {
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-subflow-convert",true);
} else {
RED.menu.setDisabled("menu-item-subflow-convert",false);
}
});
}
function createSubflow() {
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
@@ -193,9 +197,9 @@ RED.subflow = (function() {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
@@ -212,26 +216,26 @@ RED.subflow = (function() {
});
RED.workspaces.show(subflowId);
}
function convertToSubflow() {
var selection = RED.view.selection();
if (!selection.nodes) {
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
return;
}
var i;
var nodes = {};
var new_links = [];
var removedLinks = [];
var candidateInputs = [];
var candidateOutputs = [];
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
selection.nodes[0].y];
for (i=0;i<selection.nodes.length;i++) {
var n = selection.nodes[i];
nodes[n.id] = {n:n,outputs:{}};
@@ -242,14 +246,14 @@ RED.subflow = (function() {
Math.max(boundingBox[3],n.y)
]
}
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
RED.nodes.eachLink(function(link) {
if (nodes[link.source.id] && nodes[link.target.id]) {
// A link wholely within the selection
}
if (nodes[link.source.id] && !nodes[link.target.id]) {
// An outbound link from the selection
candidateOutputs.push(link);
@@ -261,7 +265,7 @@ RED.subflow = (function() {
removedLinks.push(link);
}
});
var outputs = {};
candidateOutputs = candidateOutputs.filter(function(v) {
if (outputs[v.source.id+":"+v.sourcePort]) {
@@ -274,17 +278,17 @@ RED.subflow = (function() {
return true;
});
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (candidateInputs.length > 1) {
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"error");
return;
}
//if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return;
//}
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
@@ -292,9 +296,9 @@ RED.subflow = (function() {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
@@ -322,7 +326,7 @@ RED.subflow = (function() {
}})
};
RED.nodes.addSubflow(subflow);
var subflowInstance = {
id:RED.nodes.id(),
type:"subflow:"+subflow.id,
@@ -337,13 +341,13 @@ RED.subflow = (function() {
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance);
candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link);
RED.nodes.addLink(link);
});
candidateOutputs.forEach(function(output,i) {
output.targets.forEach(function(target) {
var link = {source:subflowInstance, sourcePort:i, target: target};
@@ -351,7 +355,7 @@ RED.subflow = (function() {
RED.nodes.addLink(link);
});
});
subflow.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
@@ -366,34 +370,34 @@ RED.subflow = (function() {
RED.nodes.addLink(link);
});
});
for (i=0;i<removedLinks.length;i++) {
RED.nodes.removeLink(removedLinks[i]);
}
for (i=0;i<selection.nodes.length;i++) {
selection.nodes[i].z = subflow.id;
}
RED.history.push({
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
subflow: subflow,
activeWorkspace: RED.workspaces.active(),
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
});
RED.editor.validateNode(subflow);
RED.nodes.dirty(true);
RED.view.redraw(true);
}
return {
init: init,
createSubflow: createSubflow,

View File

@@ -14,23 +14,30 @@
* limitations under the License.
**/
RED.sidebar.config = (function() {
var content = document.createElement("div");
content.id = "tab-config";
content.style.paddingTop = "4px";
content.style.paddingLeft = "4px";
content.style.paddingRight = "4px";
var list = $("<ul>",{class:"tab-config-list"}).appendTo(content);
function init() {
RED.sidebar.addTab({
id: "config",
label: RED._("sidebar.config.label"),
name: RED._("sidebar.config.name"),
content: content,
closeable: true,
visible: false,
onchange: function() { refresh(); }
});
}
function show() {
if (!RED.sidebar.containsTab("config")) {
RED.sidebar.addTab("config",content,true);
}
refresh();
RED.sidebar.show("config");
}
function refresh() {
list.empty();
RED.nodes.eachConfig(function(node) {
@@ -46,12 +53,12 @@ RED.sidebar.config = (function() {
label = node._def.label;
}
label = label || "&nbsp;";
var entry = $('<div class="tab-config-list-entry"></div>').appendTo(li);
entry.on('dblclick',function(e) {
RED.editor.editConfig("", node.type, node.id);
});
var userArray = node.users.map(function(n) { return n.id });
entry.on('mouseover',function(e) {
RED.nodes.eachNode(function(node) {
@@ -72,12 +79,13 @@ RED.sidebar.config = (function() {
});
RED.view.redraw();
});
$('<div class="tab-config-list-label">'+label+'</div>').appendTo(entry);
$('<div class="tab-config-list-users">'+node.users.length+'</div>').appendTo(entry);
});
}
return {
init:init,
show:show,
refresh:refresh
}

View File

@@ -27,17 +27,24 @@ RED.sidebar.info = (function() {
});
var content = document.createElement("div");
content.id = "tab-info";
content.style.paddingTop = "4px";
content.style.paddingLeft = "4px";
content.style.paddingRight = "4px";
content.className = "sidebar-node-info"
var propertiesExpanded = false;
function init() {
RED.sidebar.addTab({
id: "info",
label: RED._("sidebar.info.label"),
name: RED._("sidebar.info.name"),
content: content
});
}
function show() {
if (!RED.sidebar.containsTab("info")) {
RED.sidebar.addTab("info",content,false);
}
RED.sidebar.show("info");
}
@@ -60,13 +67,13 @@ RED.sidebar.info = (function() {
function refresh(node) {
var table = '<table class="node-info"><tbody>';
table += '<tr class="blank"><td colspan="2">Node</td></tr>';
table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.node")+'</td></tr>';
if (node.type != "subflow" && node.name) {
table += "<tr><td>Name</td><td>&nbsp;"+node.name+"</td></tr>";
table += "<tr><td>"+RED._("common.label.name")+"</td><td>&nbsp;"+node.name+"</td></tr>";
}
table += "<tr><td>Type</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>ID</td><td>&nbsp;"+node.id+"</td></tr>";
table += "<tr><td>"+RED._("sidebar.info.type")+"</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>"+RED._("sidebar.info.id")+"</td><td>&nbsp;"+node.id+"</td></tr>";
var m = /^subflow(:(.+))?$/.exec(node.type);
if (m) {
var subflowNode;
@@ -75,9 +82,9 @@ RED.sidebar.info = (function() {
} else {
subflowNode = node;
}
table += '<tr class="blank"><td colspan="2">Subflow</td></tr>';
table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.subflow")+'</td></tr>';
var userCount = 0;
var subflowType = "subflow:"+subflowNode.id;
RED.nodes.eachNode(function(n) {
@@ -85,12 +92,12 @@ RED.sidebar.info = (function() {
userCount++;
}
});
table += "<tr><td>name</td><td>"+subflowNode.name+"</td></tr>";
table += "<tr><td>instances</td><td>"+userCount+"</td></tr>";
table += "<tr><td>"+RED._("common.label.name")+"</td><td>"+subflowNode.name+"</td></tr>";
table += "<tr><td>"+RED._("sidebar.info.instances")+"</td><td>"+userCount+"</td></tr>";
}
if (!m && node.type != "subflow" && node.type != "comment") {
table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> Properties</a></td></tr>';
table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> '+RED._("sidebar.info.properties")+'</a></td></tr>';
if (node._def) {
for (var n in node._def.defaults) {
if (n != "name" && node._def.defaults.hasOwnProperty(n)) {
@@ -98,7 +105,7 @@ RED.sidebar.info = (function() {
var type = typeof val;
if (type === "string") {
if (val.length === 0) {
val += '<span style="font-style: italic; color: #ccc;">blank</span>';
val += '<span style="font-style: italic; color: #ccc;">'+RED._("sidebar.info.blank")+'</span>';
} else {
if (val.length > 30) {
val = val.substring(0,30)+" ...";
@@ -114,14 +121,14 @@ RED.sidebar.info = (function() {
val += "&nbsp;"+i+": "+vv+"<br/>";
}
if (node[n].length > 10) {
val += "&nbsp;... "+node[n].length+" items<br/>";
val += "&nbsp;... "+RED._("sidebar.info.arrayItems",{count:node[n].length})+"<br/>";
}
val += "]";
} else {
val = JSON.stringify(val,jsonFilter," ");
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
}
}
@@ -139,8 +146,8 @@ RED.sidebar.info = (function() {
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
}
$("#tab-info").html(table);
$(content).html(table);
$(".node-info-property-header").click(function(e) {
var icon = $(this).find("i");
if (icon.hasClass("fa-caret-right")) {
@@ -154,16 +161,20 @@ RED.sidebar.info = (function() {
$(".node-info-property-row").hide();
propertiesExpanded = false;
}
e.preventDefault();
});
}
function clear() {
$("#tab-info").html("");
$(content).html("");
}
function set(html) {
$(content).html(html);
}
RED.view.on("selection-changed",function(selection) {
RED.events.on("view:selection-changed",function(selection) {
if (selection.nodes) {
if (selection.nodes.length == 1) {
var node = selection.nodes[0];
@@ -184,8 +195,10 @@ RED.sidebar.info = (function() {
});
return {
init: init,
show: show,
refresh:refresh,
clear: clear
clear: clear,
set: set
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,58 +13,87 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.tabs = (function() {
function createTabs(options) {
var tabs = {};
var currentTabWidth;
var currentActiveTabWidth = 0;
var ul = $("#"+options.id)
ul.addClass("red-ui-tabs");
ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab");
function onTabClick() {
activateTab($(this));
return false;
}
function onTabDblClick() {
if (options.ondblclick) {
options.ondblclick(tabs[$(this).attr('href').slice(1)]);
}
return false;
}
function activateTab(link) {
if (typeof link === "string") {
link = ul.find("a[href='#"+link+"']");
}
if (!link.parent().hasClass("active")) {
ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"});
link.parent().addClass("active");
if (options.onchange) {
options.onchange(tabs[link.attr('href').slice(1)]);
}
updateTabWidths();
setTimeout(function() {
ul.children().css({"transition": ""});
},100);
}
}
function updateTabWidths() {
var tabs = ul.find("li.red-ui-tab");
var width = ul.width();
var tabCount = tabs.size();
var tabWidth = (width-6-(tabCount*7))/tabCount;
var pct = 100*tabWidth/width;
tabs.css({width:pct+"%"});
var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = 100*tabWidth/width;
currentActiveTabWidth = currentTabWidth+"%";
if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (tabWidth < options.minimumActiveTabWidth) {
tabCount -= 1;
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
currentTabWidth = 100*tabWidth/width;
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
} else {
currentActiveTabWidth = 0;
}
}
tabs.css({width:currentTabWidth+"%"});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
} else {
ul.find(".red-ui-tab-close").show();
}
if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
}
}
ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick);
updateTabWidths();
function removeTab(id) {
var li = ul.find("a[href='#"+id+"']").parent();
if (li.hasClass("active")) {
@@ -81,20 +110,20 @@ RED.tabs = (function() {
delete tabs[id];
updateTabWidths();
}
return {
addTab: function(tab) {
tabs[tab.id] = tab;
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
link.html(tab.label);
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
if (tab.closeable) {
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
closeLink.html('<i class="fa fa-times" />');
closeLink.on("click",function(event) {
removeTab(tab.id);
});
@@ -127,7 +156,7 @@ RED.tabs = (function() {
}
}
return {
create: createTabs
}

View File

@@ -35,7 +35,7 @@ RED.view = (function() {
var activeSubflow = null;
var activeNodes = [];
var activeLinks = [];
var selected_link = null,
mousedown_link = null,
mousedown_node = null,
@@ -71,9 +71,9 @@ RED.view = (function() {
.attr("pointer-events", "all")
.style("cursor","crosshair")
.on("mousedown", function() {
this.focus();
$(this).focus();
});
var vis = outer
.append('svg:g')
.on("dblclick.zoom", null)
@@ -233,9 +233,9 @@ RED.view = (function() {
function updateActiveNodes() {
var activeWorkspace = RED.workspaces.active();
activeNodes = RED.nodes.filterNodes({z:activeWorkspace});
activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace},
target:{z:activeWorkspace}
@@ -243,7 +243,7 @@ RED.view = (function() {
}
function init() {
RED.workspaces.on("change",function(event) {
RED.events.on("workspace:change",function(event) {
var chart = $("#chart");
if (event.old !== 0) {
workspaceScrollPositions[event.old] = {
@@ -253,15 +253,15 @@ RED.view = (function() {
}
var scrollStartLeft = chart.scrollLeft();
var scrollStartTop = chart.scrollTop();
activeSubflow = RED.nodes.subflow(event.workspace);
if (activeSubflow) {
$("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0);
}
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
if (workspaceScrollPositions[event.workspace]) {
chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
chart.scrollTop(workspaceScrollPositions[event.workspace].top);
@@ -283,7 +283,7 @@ RED.view = (function() {
updateActiveNodes();
redraw();
});
$('#btn-zoom-out').click(function() {zoomOut();});
$('#btn-zoom-zero').click(function() {zoomZero();});
$('#btn-zoom-in').click(function() {zoomIn();});
@@ -296,50 +296,50 @@ RED.view = (function() {
else { zoomIn(); }
}
});
// Handle nodes dragged from the palette
$("#chart").droppable({
accept:".palette_node",
drop: function( event, ui ) {
d3.event = event;
var selected_tool = ui.draggable[0].type;
var m = /^subflow:(.+)$/.exec(selected_tool);
if (activeSubflow && m) {
var subflowId = m[1];
if (subflowId === activeSubflow.id) {
RED.notify("<strong>Error</strong>: Cannot add subflow to itself","error");
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error");
return;
}
if (RED.nodes.subflowContains(m[1],activeSubflow.id)) {
RED.notify("<strong>Error</strong>: Cannot add subflow - circular reference detected","error");
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
return;
}
}
var mousePos = d3.touches(this)[0]||d3.mouse(this);
mousePos[1] += this.scrollTop;
mousePos[0] += this.scrollLeft;
mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor;
var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:RED.workspaces.active()};
nn.type = selected_tool;
nn._def = RED.nodes.getType(nn.type);
if (!m) {
nn.inputs = nn._def.inputs || 0;
nn.outputs = nn._def.outputs;
for (var d in nn._def.defaults) {
if (nn._def.defaults.hasOwnProperty(d)) {
nn[d] = nn._def.defaults[d].value;
}
}
if (nn._def.onadd) {
nn._def.onadd.call(nn);
}
@@ -348,7 +348,7 @@ RED.view = (function() {
nn.inputs = subflow.in.length;
nn.outputs = subflow.out.length;
}
nn.changed = true;
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
RED.history.push({t:'add',nodes:[nn.id],dirty:RED.nodes.dirty()});
@@ -362,13 +362,13 @@ RED.view = (function() {
updateActiveNodes();
updateSelection();
redraw();
if (nn._def.autoedit) {
RED.editor.edit(nn);
}
}
});
RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();});
RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();});
RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();});
@@ -652,7 +652,7 @@ RED.view = (function() {
}
});
}
selected_link = null;
updateSelection();
redraw();
@@ -693,17 +693,16 @@ RED.view = (function() {
}
var selection = {};
if (moving_set.length > 0) {
selection.nodes = moving_set.map(function(n) { return n.n;});
}
if (selected_link != null) {
selection.link = selected_link;
}
eventHandler.emit("selection-changed",selection);
RED.events.emit("view:selection-changed",selection);
}
function endKeyboardMove() {
var ns = [];
for (var i=0;i<moving_set.length;i++) {
@@ -746,7 +745,7 @@ RED.view = (function() {
var removedLinks = [];
var removedSubflowOutputs = [];
var removedSubflowInputs = [];
var startDirty = RED.nodes.dirty();
if (moving_set.length > 0) {
for (var i=0;i<moving_set.length;i++) {
@@ -756,9 +755,10 @@ RED.view = (function() {
if (node.x < 0) {
node.x = 25
}
var rmlinks = RED.nodes.remove(node.id);
var removedEntities = RED.nodes.remove(node.id);
removedNodes.push(node);
removedLinks = removedLinks.concat(rmlinks);
removedNodes = removedNodes.concat(removedEntities.nodes);
removedLinks = removedLinks.concat(removedEntities.links);
} else {
if (node.direction === "out") {
removedSubflowOutputs.push(node);
@@ -789,7 +789,7 @@ RED.view = (function() {
});
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
removedLinks = removedLinks.concat(subflowRemovedLinks);
for (var j=output.i;j<activeSubflow.out.length;j++) {
activeSubflow.out[j].i--;
@@ -813,7 +813,7 @@ RED.view = (function() {
activeSubflow.in = [];
$("#workspace-subflow-add-input").toggleClass("disabled",false);
}
if (activeSubflow) {
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
n.changed = true;
@@ -827,7 +827,7 @@ RED.view = (function() {
});
RED.editor.validateNode(activeSubflow);
}
moving_set = [];
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
RED.nodes.dirty(true);
@@ -851,12 +851,25 @@ RED.view = (function() {
var nns = [];
for (var n=0;n<moving_set.length;n++) {
var node = moving_set[n].n;
// The only time a node.type == subflow can be selected is the
// input/output 'proxy' nodes. They cannot be copied.
if (node.type != "subflow") {
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
if (node._def.defaults[d].type) {
var configNode = RED.nodes.node(node[d]);
if (configNode && configNode._def.exclusive) {
nns.push(RED.nodes.convertNode(configNode));
}
}
}
}
nns.push(RED.nodes.convertNode(node));
//TODO: if the node has an exclusive config node, it should also be copied, to ensure it remains exclusive...
}
}
clipboard = JSON.stringify(nns);
RED.notify(nns.length+" node"+(nns.length>1?"s":"")+" copied");
RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}));
}
}
@@ -1043,9 +1056,9 @@ RED.view = (function() {
redraw();
}
} else if (d.changed) {
RED.notify("<strong>Warning</strong>: node has undeployed changes","warning");
RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
} else {
RED.notify("<strong>Warning</strong>: node actions disabled within subflow","warning");
RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning");
}
d3.event.preventDefault();
}
@@ -1070,9 +1083,9 @@ RED.view = (function() {
// Don't bother redrawing nodes if we're drawing links
if (mouse_mode != RED.state.JOINING) {
var dirtyNodes = {};
if (activeSubflow) {
var subflowOutputs = vis.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;});
subflowOutputs.exit().remove();
@@ -1082,7 +1095,7 @@ RED.view = (function() {
d.h=40;
});
outGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
.on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown)
.on("touchstart",function(d) {
@@ -1094,7 +1107,7 @@ RED.view = (function() {
touchStartTime = setTimeout(function() {
showTouchMenu(obj,pos);
},touchLongPressTimeout);
nodeMouseDown.call(this,d)
nodeMouseDown.call(this,d)
})
.on("touchend", function(d) {
clearTimeout(touchStartTime);
@@ -1105,7 +1118,7 @@ RED.view = (function() {
}
nodeMouseUp.call(this,d);
});
outGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",-5).attr("y",15)
.on("mousedown", function(d,i){portMouseDown(d,1,0);} )
.on("touchstart", function(d,i){portMouseDown(d,1,0);} )
@@ -1125,7 +1138,7 @@ RED.view = (function() {
d.h=40;
});
inGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
.on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown)
.on("touchstart",function(d) {
@@ -1137,7 +1150,7 @@ RED.view = (function() {
touchStartTime = setTimeout(function() {
showTouchMenu(obj,pos);
},touchLongPressTimeout);
nodeMouseDown.call(this,d)
nodeMouseDown.call(this,d)
})
.on("touchend", function(d) {
clearTimeout(touchStartTime);
@@ -1148,7 +1161,7 @@ RED.view = (function() {
}
nodeMouseUp.call(this,d);
});
inGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",35).attr("y",15)
.on("mousedown", function(d,i){portMouseDown(d,0,i);} )
.on("touchstart", function(d,i){portMouseDown(d,0,i);} )
@@ -1157,9 +1170,9 @@ RED.view = (function() {
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input");
subflowOutputs.each(function(d,i) {
if (d.dirty) {
var output = d3.select(this);
@@ -1183,7 +1196,7 @@ RED.view = (function() {
vis.selectAll(".subflowoutput").remove();
vis.selectAll(".subflowinput").remove();
}
var node = vis.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
node.exit().remove();
@@ -1211,17 +1224,17 @@ RED.view = (function() {
.attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; })
.attr("class",function(d) { return "node_button "+((d._def.align == "right") ? "node_right_button" : "node_left_button"); });
nodeButtonGroup.append('rect')
.attr("rx",8)
.attr("ry",8)
.attr("rx",5)
.attr("ry",5)
.attr("width",32)
.attr("height",node_height-4)
.attr("fill","#eee");//function(d) { return d._def.color;})
nodeButtonGroup.append('rect')
.attr("class","node_button_button")
.attr("x",function(d) { return d._def.align == "right"? 10:5})
.attr("x",function(d) { return d._def.align == "right"? 11:5})
.attr("y",4)
.attr("rx",5)
.attr("ry",5)
.attr("rx",4)
.attr("ry",4)
.attr("width",16)
.attr("height",node_height-12)
.attr("fill",function(d) { return d._def.color;})
@@ -1243,8 +1256,8 @@ RED.view = (function() {
var mainRect = node.append("rect")
.attr("class", "node")
.classed("node_unknown",function(d) { return d.type == "unknown"; })
.attr("rx", 6)
.attr("ry", 6)
.attr("rx", 5)
.attr("ry", 5)
.attr("fill",function(d) { return d._def.color;})
.on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown)
@@ -1309,7 +1322,7 @@ RED.view = (function() {
.attr("class","node_icon_shade_border")
.attr("stroke-opacity","0.1")
.attr("stroke","#000")
.attr("stroke-width","2");
.attr("stroke-width","1");
if ("right" == d._def.align) {
icon_group.attr('class','node_icon_group node_icon_group_'+d._def.align);
@@ -1318,7 +1331,7 @@ RED.view = (function() {
//icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align);
//icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align);
}
//if (d.inputs > 0 && d._def.align == null) {
// icon_shade.attr("width",35);
// icon.attr("transform","translate(5,0)");
@@ -1388,7 +1401,7 @@ RED.view = (function() {
var thisNode = d3.select(this);
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
if (mouse_mode != RED.state.MOVING_ACTIVE) {
thisNode.selectAll(".node")
.attr("width",function(d){return d.w})
@@ -1398,13 +1411,13 @@ RED.view = (function() {
;
//thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
//thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"});
thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38});
//thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
//thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;});
//thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
var inputPorts = thisNode.selectAll(".port_input");
if (d.inputs === 0 && !inputPorts.empty()) {
inputPorts.remove();
@@ -1419,13 +1432,13 @@ RED.view = (function() {
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
}
var numOutputs = d.outputs;
var y = (d.h/2)-((numOutputs-1)/2)*13;
d.ports = d.ports || d3.range(numOutputs);
d._ports = thisNode.selectAll(".port_output").data(d.ports);
var output_group = d._ports.enter().append("g").attr("class","port_output");
output_group.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
.on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
@@ -1433,7 +1446,7 @@ RED.view = (function() {
.on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
d._ports.exit().remove();
if (d._ports) {
numOutputs = d.outputs || 1;
@@ -1461,7 +1474,7 @@ RED.view = (function() {
(d._def.align?' node_label_'+d._def.align:'')+
(d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
});
if (d._def.icon) {
icon = thisNode.select(".node_icon");
var current_url = icon.attr("xlink:href");
@@ -1482,27 +1495,27 @@ RED.view = (function() {
}
}
}
thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;});
thisNode.selectAll(".node_changed")
.attr("x",function(d){return d.w-10})
.classed("hidden",function(d) { return !d.changed; });
thisNode.selectAll(".node_error")
.attr("x",function(d){return d.w-10-(d.changed?13:0)})
.classed("hidden",function(d) { return d.valid; });
thisNode.selectAll(".port_input").each(function(d,i) {
var port = d3.select(this);
port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";})
});
thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
thisNode.selectAll('.node_button').attr("opacity",function(d) {
return (activeSubflow||d.changed)?0.4:1
});
@@ -1522,11 +1535,11 @@ RED.view = (function() {
}
return 1;
});
//thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) {
// return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color)
//});
thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";});
thisNode.selectAll('text.node_badge_label').text(function(d,i) {
if (d._def.badge) {
@@ -1581,7 +1594,7 @@ RED.view = (function() {
}
);
var linkEnter = link.enter().insert("g",".node").attr("class","link");
linkEnter.each(function(d,i) {
var l = d3.select(this);
d.added = true;
@@ -1608,7 +1621,7 @@ RED.view = (function() {
l.append("svg:path").attr("class","link_line link_path")
.classed("link_subflow", function(d) { return activeSubflow && (d.source.type === "subflow" || d.target.type === "subflow") });
});
link.exit().remove();
var links = vis.selectAll(".link_path");
links.each(function(d) {
@@ -1618,7 +1631,7 @@ RED.view = (function() {
var numOutputs = d.source.outputs || 1;
var sourcePort = d.sourcePort || 0;
var y = -((numOutputs-1)/2)*13 +13*sourcePort;
var dy = d.target.y-(d.source.y+y);
var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2);
var delta = Math.sqrt(dy*dy+dx*dx);
@@ -1627,19 +1640,19 @@ RED.view = (function() {
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
}
if (dx < 0) {
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
if (Math.abs(dy) < 3*node_height) {
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
}
}
d.x1 = d.source.x+d.source.w/2;
d.y1 = d.source.y+y;
d.x2 = d.target.x-d.target.w/2;
d.y2 = d.target.y;
return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+
" C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+
(d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+
@@ -1647,9 +1660,9 @@ RED.view = (function() {
});
}
})
link.classed("link_selected", function(d) { return d === selected_link || d.selected; });
link.classed("link_unknown",function(d) {
link.classed("link_unknown",function(d) {
delete d.added;
return d.target.type == "unknown" || d.source.type == "unknown"
});
@@ -1662,12 +1675,12 @@ RED.view = (function() {
}
).classed("link_selected", false);
}
if (d3.event) {
d3.event.preventDefault();
}
}
function focusView() {
@@ -1688,7 +1701,7 @@ RED.view = (function() {
var new_links = result[1];
var new_workspaces = result[2];
var new_subflows = result[3];
var new_ms = new_nodes.filter(function(n) { return n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
var new_node_ids = new_nodes.map(function(n){ return n.id; });
@@ -1754,36 +1767,15 @@ RED.view = (function() {
} catch(error) {
if (error.code != "NODE_RED") {
console.log(error.stack);
RED.notify("<strong>Error</strong>: "+error,"error");
RED.notify(RED._("notification.error",{message:error.toString()}),"error");
} else {
RED.notify("<strong>Error</strong>: "+error.message,"error");
RED.notify(RED._("notification.error",{message:error.message}),"error");
}
}
}
// TODO: DRY
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
return {
init: init,
on: eventHandler.on,
state:function(state) {
if (state == null) {
return mouse_mode
@@ -1791,13 +1783,13 @@ RED.view = (function() {
mouse_mode = state;
}
},
redraw: function(updateActive) {
if (updateActive) {
updateActiveNodes();
}
RED.workspaces.refresh();
redraw();
redraw();
},
focus: focusView,
importNodes: importNodes,

View File

@@ -16,7 +16,7 @@
RED.workspaces = (function() {
var activeWorkspace = 0;
var workspaceIndex = 0;
@@ -28,9 +28,10 @@ RED.workspaces = (function() {
var tabId = RED.nodes.id();
do {
workspaceIndex += 1;
} while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
//TODO: nls of Sheet
} while($("#workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
ws = {type:"tab",id:tabId,label:RED._('workspace.defaultName',{number:workspaceIndex})};
RED.nodes.addWorkspace(ws);
workspace_tabs.addTab(ws);
workspace_tabs.activateTab(tabId);
@@ -56,10 +57,10 @@ RED.workspaces = (function() {
RED.nodes.dirty(true);
} else {
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
$( "#node-dialog-delete-workspace-name" ).text(ws.label);
$( "#node-dialog-delete-workspace-content" ).text(RED._("workspace.delete",{label:ws.label}));
$( "#node-dialog-delete-workspace" ).dialog('open');
}
}
}
function showRenameWorkspaceDialog(id) {
var ws = RED.nodes.workspace(id);
$( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
@@ -77,149 +78,140 @@ RED.workspaces = (function() {
$( "#node-input-workspace-name" ).val(ws.label);
$( "#node-dialog-rename-workspace" ).dialog("open");
}
var workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
if (tab.type == "subflow") {
$("#workspace-toolbar").show();
} else {
$("#workspace-toolbar").hide();
}
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
eventHandler.emit("change",event);
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id);
} else {
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
}
},
onadd: function(tab) {
RED.menu.addItem("menu-item-workspace",{
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label,
onselect:function() {
workspace_tabs.activateTab(tab.id);
}
});
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
},
onremove: function(tab) {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
}
});
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
$( "#node-dialog-rename-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: "Rename sheet",
buttons: [
{
class: 'leftButton',
text: "Delete",
click: function() {
var workspace = $(this).dialog('option','workspace');
$( this ).dialog( "close" );
deleteWorkspace(workspace);
}
},
{
text: "Ok",
click: function() {
var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val();
if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu
}
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
$( "#node-dialog-delete-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: "Confirm delete",
buttons: [
{
text: "Ok",
click: function() {
var workspace = $(this).dialog('option','workspace');
deleteWorkspace(workspace,true);
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
var workspace_tabs;
function createWorkspaceTabs(){
workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
if (tab.type == "subflow") {
$("#chart").css({"margin-top": "40px"});
$("#workspace-toolbar").show();
} else {
$("#workspace-toolbar").hide();
$("#chart").css({"margin-top": "0"});
}
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
RED.events.emit("workspace:change",event);
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id);
} else {
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
}
},
onadd: function(tab) {
RED.menu.addItem("menu-item-workspace",{
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label,
onselect:function() {
workspace_tabs.activateTab(tab.id);
}
});
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
},
onremove: function(tab) {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
},
minimumActiveTabWidth: 150
});
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
$( "#node-dialog-rename-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: RED._("workspace.renameSheet"),
buttons: [
{
class: 'leftButton',
text: RED._("common.label.delete"),
click: function() {
var workspace = $(this).dialog('option','workspace');
$( this ).dialog( "close" );
deleteWorkspace(workspace);
}
},
{
text: RED._("common.label.ok"),
click: function() {
var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val();
if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu
}
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
$( "#node-dialog-delete-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: RED._("workspace.confirmDelete"),
buttons: [
{
text: RED._("common.label.ok"),
click: function() {
var workspace = $(this).dialog('option','workspace');
deleteWorkspace(workspace,true);
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
}
function init() {
createWorkspaceTabs();
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
RED.sidebar.on("resize",workspace_tabs.resize);
RED.events.on("sidebar:resize",workspace_tabs.resize);
RED.menu.setAction('menu-item-workspace-delete',function() {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
});
$(window).resize(function() {
workspace_tabs.resize();
});
}
// TODO: DRY
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
function removeWorkspace(ws) {
if (!ws) {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
@@ -231,10 +223,9 @@ RED.workspaces = (function() {
}
return {
init: init,
on: eventHandler.on,
add: addWorkspace,
remove: removeWorkspace,
edit: function(id) {
showRenameWorkspaceDialog(id||activeWorkspace);
},
@@ -251,15 +242,15 @@ RED.workspaces = (function() {
if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id);
if (sf) {
addWorkspace({type:"subflow",id:id,label:"Subflow: "+sf.name, closeable: true});
addWorkspace({type:"subflow",id:id,label:RED._("subflow.tabLabel",{name:sf.name}), closeable: true});
}
}
}
workspace_tabs.activateTab(id);
},
refresh: function() {
RED.nodes.eachSubflow(function(sf) {
if (workspace_tabs.contains(sf.id)) {
workspace_tabs.renameTab(sf.id,"Subflow: "+sf.name);
workspace_tabs.renameTab(sf.id,RED._("subflow.tabLabel",{name:sf.name}));
}
});
},

View File

@@ -14,13 +14,13 @@
* limitations under the License.
**/
RED.user = (function() {
function login(opts,done) {
if (typeof opts == 'function') {
done = opts;
opts = {};
}
var dialog = $('<div id="node-dialog-login" class="hide">'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
@@ -37,7 +37,7 @@ RED.user = (function() {
resizable: false,
draggable: false
});
$("#node-dialog-login-fields").empty();
$.ajax({
dataType: "json",
@@ -45,7 +45,7 @@ RED.user = (function() {
success: function(data) {
if (data.type == "credentials") {
var i=0;
if (data.image) {
$("#node-dialog-login-image").attr("src",data.image);
} else {
@@ -56,7 +56,7 @@ RED.user = (function() {
var row = $("<div/>",{id:"rrr"+i,class:"form-row"});
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
if (i<data.prompts.length-1) {
input.keypress(
(function() {
@@ -72,21 +72,21 @@ RED.user = (function() {
}
row.appendTo("#node-dialog-login-fields");
}
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+
'<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="Login"></div>').appendTo("#node-dialog-login-fields");
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">'+RED._("user.loginFailed")+'</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">'+RED._("common.label.cancel")+'</a>':'')+
'<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="'+RED._("user.login")+'"></div>').appendTo("#node-dialog-login-fields");
$("#node-dialog-login-submit").button();
$("#node-dialog-login-fields").submit(function(event) {
$("#node-dialog-login-submit").button("option","disabled",true);
$("#node-dialog-login-failed").hide();
$(".login-spinner").show();
var body = {
client_id: "node-red-editor",
grant_type: "password",
scope:"*"
scope:""
}
for (var i=0;i<data.prompts.length;i++) {
var field = data.prompts[i];
@@ -116,7 +116,7 @@ RED.user = (function() {
}
}
dialog.dialog("open");
}
}
});
}
@@ -131,17 +131,17 @@ RED.user = (function() {
}
})
}
function updateUserMenu() {
$("#usermenu-submenu li").remove();
if (RED.settings.user.anonymous) {
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-login",
label:"Login",
label:RED._("menu.label.login"),
onselect: function() {
RED.user.login({cancelable:true},function() {
RED.settings.load(function() {
RED.notify("Logged in as "+RED.settings.user.username,"success");
RED.notify(RED._("user.loggedInAs",{name:RED.settings.user.username}),"success");
updateUserMenu();
});
});
@@ -154,31 +154,31 @@ RED.user = (function() {
});
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-logout",
label:"Logout",
label:RED._("menu.label.logout"),
onselect: function() {
RED.user.logout();
}
});
}
}
function init() {
if (RED.settings.user) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
.prependTo(".header-toolbar");
RED.menu.init({id:"btn-usermenu",
options: []
});
updateUserMenu();
}
}
}
return {
init: init,

45
editor/sass/colors.scss Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright 2015 IBM Corp.
*
* 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.
**/
$form-placeholder-color: #bbbbbb;
$form-text-color: #444;
$form-input-focus-color: rgba(85,150,230,0.8);
$form-input-border-color: #ccc;
$node-selected-color: #ff7f0e;
$port-selected-color: #ff7f0e;
$link-color: #888;
$link-subflow-color: #bbb;
$link-unknown-color: #f00;
$primary-border-color: #bbbbbb;
$secondary-border-color: #dddddd;
$tab-background-active: #fff;
$tab-background-inactive: #f0f0f0;
$tab-background-hover: #ddd;
$palette-header-background: #f3f3f3;
$workspace-button-background: #fff;
$workspace-button-background-hover: #ddd;
$workspace-button-background-active: #efefef;
$workspace-button-color: #999;
$workspace-button-color-disabled: #ccc;
$workspace-button-color-focus: #999;
$workspace-button-color-hover: #666;
$workspace-button-color-active: #666;

View File

@@ -29,7 +29,8 @@
.form-row {
clear: both;
margin-bottom:10px;
color: $form-text-color;
margin-bottom:12px;
}
.form-row label {
display: inline-block;
@@ -39,32 +40,21 @@
width:70%;
}
input.input-append-left {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
button.input-append-right {
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
border-top-right-radius: 4px !important;
border-bottom-right-radius: 4px !important;
margin-left: -1px !important;
padding-left: 4px !important;
padding-right: 4px !important;
}
.form-tips {
background: lightgoldenrodyellow;
background: #ffe;
font-size: 12px;
padding: 8px;
border-radius: 5px;
border: 1px solid #999;
border-radius: 2px;
border: 1px solid $secondary-border-color;
max-width: 450px;
}
.form-tips code {
border: none;
padding: auto;
}
.form-tips a {
text-decoration: underline;
}
.node-text-editor {
border:1px solid #ccc;
@@ -74,3 +64,18 @@ button.input-append-right {
font-family: monospace !important;
}
.editor-button {
@include workspace-button;
height: 34px;
line-height: 30px;
font-size: 13px;
border-radius: 4px;
padding: 0 10px;
}
.editor-button-small {
height: 20px;
line-height: 18px;
font-size: 10px;
border-radius: 2px;
padding: 0 5px;
}

View File

@@ -49,10 +49,6 @@
margin-left: 20px;
}
#workspace {
@include component-border;
}
.node_label_italic {
font-style: italic;
}
@@ -90,7 +86,7 @@
.node {
stroke: #999;
cursor: move;
stroke-width: 2;
stroke-width: 1;
}
.node_unknown {
stroke-dasharray:10,4;
@@ -116,11 +112,11 @@
.node_button {
fill: inherit;
}
.port {
stroke: #999;
stroke-width: 2;
stroke-width: 1;
fill: #ddd;
cursor: crosshair;
}
@@ -152,25 +148,26 @@
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
}
.node_invalid {
stroke: #ff0000;
}
.node_selected {
stroke: #ff7f0e !important;
stroke-width: 2;
stroke: $node-selected-color !important;
}
.node_highlighted {
stroke: #dd1616;
stroke-width: 3;
stroke-width: 2;
stroke-dasharray: 10, 4;
}
.node_hovered {
}
.port_hovered {
stroke: #ff7f0e;
fill: #ff7f0e;
stroke: $port-selected-color;
fill: $port-selected-color;
}
.subflowport {
stroke-dasharray: 5,5;
@@ -179,35 +176,35 @@
}
.drag_line {
stroke: #ff7f0e;
stroke-width: 5;
stroke: $node-selected-color;
stroke-width: 4;
fill: none;
pointer-events: none;
}
.drag_line_hidden {
stroke: #ff7f0e;
stroke: $node-selected-color;
stroke-width: 0;
pointer-events: none;
fill: none;
}
.link_line {
stroke: #7f7f7f;
stroke-width: 4;
stroke: $link-color;
stroke-width: 3;
fill: none;
pointer-events: none;
}
.link_subflow {
stroke: #bbb;
stroke: $link-subflow-color;
stroke-dasharray: 10,5;
stroke-width: 3;
stroke-width: 2;
}
.link_outline {
stroke: #fff;
stroke-width: 6;
stroke-width: 4;
cursor: crosshair;
fill: none;
pointer-events: none;
@@ -215,17 +212,16 @@
.link_background {
stroke: #fff;
opacity: 0;
stroke-width: 25;
stroke-width: 20;
cursor: crosshair;
fill: none;
}
g.link_selected path.link_line {
stroke: #ff7f0e;
stroke: $node-selected-color;
}
g.link_unknown path.link_line {
stroke: #f00;
stroke: $link-unknown-color;
stroke-width: 2;
stroke-dasharray: 10, 4;
}

1046
editor/sass/forms.scss Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -27,23 +27,22 @@
.ui-dialog {
border-radius: 1px;
border: 1px solid #eee;
background: #fff;
padding: 0;
box-shadow: 2px 2px 12px rgba(0,0,0,0.2);
@include component-shadow;
}
.ui-dialog .ui-dialog-content {
padding: 25px 25px 10px 25px;
}
.ui-dialog .ui-dialog-titlebar {
padding: 10px;
background: #f0f0f0;
background: #f3f3f3;
border: none;
border-bottom: 2px solid #888;
border-bottom: 1px solid #999;
border-radius: 0;
}
.ui-corner-all {
border-radius: 2px;
border-radius: 1px;
}
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default {
background: #f3f3f3;

View File

@@ -23,7 +23,62 @@
}
@mixin component-border {
border: 1px solid #000;
border-radius: 3px;
border: 1px solid $primary-border-color;
box-sizing: border-box;
}
@mixin workspace-button {
@include disable-selection;
color: $workspace-button-color;
box-sizing: border-box;
display: inline-block;
background: $workspace-button-background;
border: 1px solid $secondary-border-color;
text-align: center;
margin:0;
text-decoration: none;
cursor:pointer;
&.disabled {
cursor: default;
color: $workspace-button-color-disabled;
}
&:not(.disabled):hover {
text-decoration: none;
color: $workspace-button-color-hover;
background: $workspace-button-background-hover;
}
&:not(.disabled):focus {
color: $workspace-button-color-focus;
}
&:not(.disabled):active {
color: $workspace-button-color-active;
background: $workspace-button-background-active;
}
}
@mixin component-footer {
border-top: 1px solid $primary-border-color;
background: #f3f3f3;
text-align: right;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 25px;
line-height: 23px;
padding: 0 10px;
}
@mixin component-footer-button {
@include workspace-button;
font-size: 11px;
line-height: 17px;
width: 18px;
height: 18px;
}
@mixin component-shadow {
border: 1px solid $secondary-border-color;
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
}

View File

@@ -14,10 +14,6 @@
* limitations under the License.
**/
.notification {
position: absolute;
}
#notifications {
z-index: 10000;
width: 500px;
@@ -26,7 +22,24 @@
position: absolute;
top: 1px;
}
#notifications .alert {
box-shadow: 0 0 1px 1px;
margin-bottom: 5px;
.notification {
box-sizing: border-box;
position: relative;
padding: 14px 18px;
margin-bottom: 4px;
box-shadow: 0 1px 1px 1px rgba(0,0,0, 0.15);
background-color: #fff;
color: #666;
border: 1px solid #325C80;
border-left-width: 16px;
}
.notification-success {
border-color: #4B8400;
}
.notification-warning {
border-color: #D74108;
}
.notification-error {
border-color: #AD1625;
}

View File

@@ -17,24 +17,24 @@
#palette {
position: absolute;
top: 5px;
bottom: 10px;
left:10px;
top: 0px;
bottom: 0px;
left:0px;
background: #f3f3f3;
width: 170px;
width: 180px;
text-align: center;
@include disable-selection;
@include component-border;
}
.palette-scroll {
display: none;
position: absolute;
top: 0;
top: 35px;
right: 0;
bottom: 35px;
bottom: 25px;
left:0;
padding: 5px;
padding: 0;
overflow-y: auto;
box-sizing:border-box;
}
@@ -44,32 +44,40 @@
#palette-search {
position: absolute;
display: none;
bottom: 0;
top: 0;
left:0;
right:0;
overflow: hidden;
background: #f3f3f3;
background: #ffffff;
text-align: center;
height: 35px;
padding: 3px;
border-top: 1px solid #999;
border-bottom: 1px solid $primary-border-color;
box-sizing:border-box;
}
#palette-search i {
font-size: 10px;
color: #666;
}
#palette-search i.fa-search {
position: absolute;
pointer-events: none;
left: 4px;
top: 10px;
left: 12px;
top: 12px;
}
#palette-search i.fa-times {
position: absolute;
right: 6px;
top: 10px;
right: 7px;
top: 12px;
}
#palette-search-clear {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 20px;
display: none;
color: #000;
}
#palette-search input {
@@ -78,7 +86,7 @@
width: 100%;
box-shadow: none;
-webkit-box-shadow: none;
padding: 3px 17px;
padding: 3px 17px 3px 22px;
margin: 0px;
height: 30px;
box-sizing:border-box;
@@ -89,27 +97,31 @@
box-shadow: none;
-webkit-box-shadow: none;
}
#palette-footer {
@include component-footer;
}
.palette-button {
@include component-footer-button;
}
.palette-category {
border: 1px solid #999;
border-radius: 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ccc;
}
.palette-content {
background: #fff;
border-top: 1px solid #aaa;
padding-bottom: 3px;
padding: 3px;
}
.palette-header {
background: #f3f3f3;
border-radius: 3px;
background: $palette-header-background;
cursor: pointer;
text-align: left;
padding: 1px;
padding: 9px;
font-weight: bold;
}
.palette-header i {
margin: 3px 4px 3px 3px;
margin: 3px 10px 3px 3px;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
@@ -126,6 +138,7 @@
clear: both;
}
.palette_label {
font-size: 13px;
margin: 4px 0 4px 28px;
line-height: 20px;
overflow: hidden;
@@ -137,12 +150,11 @@
.palette_node {
cursor:move;
font-size:13px;
background: #ddd;
margin: 10px auto;
height: 25px;
border-radius: 6px;
border: 2px solid #999;
border-radius: 5px;
border: 1px solid #999;
background-position: 5% 50%;
background-repeat: no-repeat;
width: 120px;
@@ -150,7 +162,7 @@
position: relative;
}
.palette_node:hover {
border-color: #ff7f0e;
border-color: $node-selected-color;
background-color: #eee;
}
.palette_port {
@@ -181,7 +193,7 @@
bottom:0;
left:0;
width: 30px;
border-right: 2px solid rgba(0,0,0,0.1);
border-right: 1px solid rgba(0,0,0,0.1);
background-color: rgba(0,0,0,0.05);
}
.palette_icon_container_right {

53
editor/sass/popover.scss Normal file
View File

@@ -0,0 +1,53 @@
/**
* Copyright 2015 IBM Corp.
*
* 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.
**/
.red-ui-popover {
display: none;
position: absolute;
width: 300px;
padding: 10px;
height: auto;
background: #fff;
z-index: 1000;
font-size: 14px;
line-height: 1.4em;
@include component-shadow;
}
.red-ui-popover:after, .red-ui-popover:before {
right: 100%;
top: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.red-ui-popover:after {
border-color: rgba(136, 183, 213, 0);
border-right-color: #fff;
border-width: 10px;
margin-top: -10px;
}
.red-ui-popover:before {
border-color: rgba(194, 225, 245, 0);
border-right-color: $primary-border-color;
border-width: 11px;
margin-top: -11px;
}

View File

@@ -14,14 +14,12 @@
* limitations under the License.
**/
#sidebar {
position: absolute;
top: 5px;
right: 10px;
bottom: 10px;
width: 305px;
top: 0px;
right: 0px;
bottom: 0px;
width: 315px;
background: #fff;
box-sizing: border-box;
@include component-border;
@@ -35,26 +33,36 @@
#sidebar-content {
position: absolute;
top: 30px;
top: 35px;
right: 0;
bottom: 1px;
bottom: 25px;
left: 0px;
font-size: 1.2em;
padding-top: 3px;
overflow-y: auto;
}
#sidebar-separator {
position: absolute;
top: 5px;
right: 316px;
right: 315px;
bottom:10px;
width: 15px;
width: 7px;
background: url(images/grip.png) no-repeat 50% 50%;
cursor: col-resize;
}
.sidebar-closed > #sidebar { display: none; }
.sidebar-closed > #sidebar-separator { right: 0px !important; }
.sidebar-closed > #workspace { right: 15px !important; }
.sidebar-closed > #chart-zoom-controls { right: 35px !important; }
.sidebar-closed > #workspace { right: 7px !important; }
#sidebar .button {
@include workspace-button;
line-height: 18px;
font-size: 12px;
margin-right: 5px;
padding: 2px 8px;
}
#sidebar-footer {
@include component-footer;
}

View File

@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@import "colors";
@import "mixins";
@import "forms";
@import "jquery";
@import "bootstrap";
@@ -35,7 +38,7 @@
@import "tabs";
@import "tab-config";
@import "tab-info";
@import "popover";
@import "flow";
@import "dragdrop";
@@ -44,14 +47,14 @@
body {
font: 13px "Helvetica" !important;
font: 14px "Helvetica" !important;
padding-top: 100px;
background: url("images/pw_maze_white.png");
background: #f3f3f3;
}
#main-container {
position: absolute;
top:50px; left:0; bottom: 0; right:0;
top:40px; left:0; bottom: 0; right:0;
overflow:hidden;
}
@@ -66,5 +69,42 @@ i.spinner {
background-size: contain
}
code, pre {
padding: 0 3px 2px;
font-family: monospace;
font-size: 14px;
color: #333333;
border-radius: 1px;
}
code {
padding: 0px 4px;
color: #AD1625;
white-space: nowrap;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
}
pre {
display: block;
padding: 9.5px;
margin: 0 0 10px;
line-height: 20px;
word-break: break-all;
word-wrap: break-word;
white-space: pre;
white-space: pre-wrap;
background-color: #f5f5f5;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 2px;
}
pre code {
padding: 0;
color: inherit;
white-space: pre;
white-space: pre-wrap;
background-color: transparent;
border: 0;
}

View File

@@ -14,9 +14,12 @@
* limitations under the License.
**/
.sidebar-node-info hr {
margin: 10px 0;
}
table.node-info {
font-size: 14px;
margin: 5px;
margin: 0px;
width: 97%;
}
table.node-info tr {
@@ -42,36 +45,39 @@ table.node-info td:last-child{
padding-left: 5px;
color: #666;
}
div.node-info {
margin: 5px;
}
.node-info-property-header {
color: #666;
}
.node-info-property-header:hover,
.node-info-property-header:focus {
color: #666;
text-decoration: none;
}
.node-help {
font-size: 16px;
font-size: 14px;
line-height: 1.5em;
h1 {
font-weight: normal;
font-size: 1.953em;
font-size: 23px;
margin: 8px auto;
}
h2 {
font-weight: normal;
font-size: 1.563em;
font-size: 18px;
margin: 8px auto;
}
h3 {
font-weight: normal;
font-size: 16px;
margin: 8px auto;
}
h3,
h4,
h5 {
font-weight: normal;
font-size: 1.25em;
font-size: 14px;
margin: 8px auto;
}
}

View File

@@ -16,12 +16,13 @@
ul.red-ui-tabs {
list-style-type: none;
padding:5px 2px 0px 5px;
padding:0;
margin: 0;
display: block;
height: 24px;
border-bottom: 1px solid #999;
background: #e3e3e3;
height: 35px;
box-sizing:border-box;
border-bottom: 1px solid $primary-border-color;
background: #fff;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
@@ -30,17 +31,16 @@ ul.red-ui-tabs {
}
ul.red-ui-tabs li {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
box-sizing: border-box;
display: inline-block;
border-left: 1px solid #999;
border-top: 1px solid #999;
border-right: 1px solid #999;
border-bottom: 1px solid #999;
background: #e3e3e3;
margin: 0 5px 0 0;
height: 23px;
line-height: 17px;
border-left: 1px solid $primary-border-color;
border-top: 1px solid $primary-border-color;
border-right: 1px solid $primary-border-color;
border-bottom: 1px solid $primary-border-color;
background: $tab-background-inactive;
margin: 3px 3px 0 3px;
height: 32px;
line-height: 29px;
max-width: 200px;
width: 14%;
overflow: hidden;
@@ -49,53 +49,64 @@ ul.red-ui-tabs li {
ul.red-ui-tabs li a.red-ui-tab-label {
display: block;
padding: 3px 16px;
padding-left: 12px;
width: 100%;
height: 100%;
color: #666;
}
ul.red-ui-tabs li {
position: relative;
}
ul.red-ui-tabs li a.red-ui-tab-close {
background: rgba(227,227,227,0.8);
.red-ui-tab-close {
background: $tab-background-inactive;
opacity: 0.8;
position: absolute;
right: 2px;
top: 2px;
right: 0px;
top: 0px;
display: block;
width: 20px;
height: 20px;
line-height: 20px;
height: 30px;
line-height: 28px;
text-align: center;
padding: 0px;
border-radius: 5px;
color: #666;
color: #aaa;
&:hover {
background: $workspace-button-background-hover !important;
opacity: 1;
}
}
ul.red-ui-tabs li a.red-ui-tab-close:hover {
background: #bbb !important;
ul.red-ui-tabs li:not(.active) a:hover+a.red-ui-tab-close {
background: $tab-background-hover;
}
ul.red-ui-tabs li.active a.red-ui-tab-close {
color: #aaa;
background: $tab-background-active;
&:hover {
background: $workspace-button-background-hover !important;
color: $workspace-button-color-hover;
}
}
ul.red-ui-tabs li a:hover {
text-decoration: none;
background: #f0f0f0;
}
ul.red-ui-tabs li:not(.active) a:hover {
color: $workspace-button-color-hover;
background: $tab-background-hover;
}
ul.red-ui-tabs li a:focus {
text-decoration: none;
}
ul.red-ui-tabs li.active {
background: #fff;
background: $tab-background-active;
font-weight: bold;
border-bottom: 1px solid #fff;
}
ul.red-ui-tabs li.active a {
color: #333;
}
ul.red-ui-tabs li.active a.red-ui-tab-close {
background: rgba(255,255,255,0.8);
}
ul.red-ui-tabs li.active a.red-ui-tab-label:hover {
background: #fff;
}
ul.red-ui-tabs li.red-ui-add-tab {
width: 25px;
border-top-right-radius: 15px;
line-height: 22px;
}
ul.red-ui-tabs li.red-ui-add-tab a {
padding: 2px 4px;
}

View File

@@ -19,11 +19,13 @@
overflow: auto;
background: #e3e3e3;
position: absolute;
bottom:0px;
top: 30px;
bottom:25px;
top: 35px;
left:0px;
right:0px;
box-sizing:border-box;
}
#chart svg:focus {
outline: none;
}
@@ -31,46 +33,43 @@
#workspace {
position: absolute;
margin: 0;
top:5px;
left:190px;
bottom: 10px;
right: 330px;
top:0px;
left:179px;
bottom: 0px;
right: 322px;
overflow: hidden;
@include component-border;
}
#chart-zoom-controls {
position: absolute;
bottom:30px; right: 350px;
}
#chart-zoom-controls {
padding-top: 3px;
text-align: right;
float: right;
.workspace-footer-button {
@include component-footer-button;
}
#workspace-tabs {
margin-right: 28px;
margin-right: 35px;
}
#workspace-add-tab {
position: absolute;
box-sizing: border-box;
top: 0;
right: 0;
height: 29px;
width: 28px;
border-bottom: 1px solid #999;
height: 35px;
width: 35px;
background: #fff;
border-bottom: 1px solid $primary-border-color;
}
#btn-workspace-add-tab {
display: inline-block;
width: 100%;
background: #e3e3e3;
height: 100%;
line-height: 30px;
text-align: center;
color: #000;
}
#btn-workspace-add-tab:hover {
background: #efefef;
@include workspace-button;
line-height: 32px;
height: 32px;
width: 32px;
margin-top: 3px;
margin-right:3px;
border: 1px solid $primary-border-color;
}
#workspace-footer {
@include component-footer;
}

View File

@@ -14,41 +14,24 @@
* limitations under the License.
**/
#workspace-toolbar {
display: none;
position: absolute;
top: 30px;
top: 35px;
left:0;
right: 20px;
width: 100%;
padding: 7px;
border-bottom-right-radius: 5px;
background: #f3f3f3;
height: 40px;
box-sizing: border-box;
background: #fff;
border-bottom: 1px solid $secondary-border-color;
}
#workspace-toolbar .button {
@include workspace-button;
line-height: 18px;
display: inline-block;
font-size: 12px;
padding: 2px 8px;
text-decoration: none;
border-radius: 3px;
color: #666;
background: #f6f6f6;
vertical-align: middle;
box-shadow: 0 0 2px #888;
margin-right: 5px;
}
#workspace-toolbar .button.disabled {
box-shadow: 0 0 2px #bbb;
color: #aaa;
cursor: default;
}
#workspace-toolbar .button:not(.disabled):hover {
background: #e6e6e6;
box-shadow: 0 0 2px #666;
}
#workspace-toolbar .button:not(.disabled):active {
background: #e0e0e0;
box-shadow: 0 0 2px #444;
padding: 2px 8px;
}

View File

@@ -43,11 +43,15 @@
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-container" class="palette-scroll">
</div>
<div id="palette-search">
<i class="fa fa-search"></i><input id="palette-search-input" type="text" placeholder="filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
<i class="fa fa-search"></i><input id="palette-search-input" type="text" data-i18n="[placeholder]palette.filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
</div>
<div id="palette-container" class="palette-scroll"></div>
<div id="palette-footer">
<a class="palette-button" id="palette-collapse-all" href="#"><i class="fa fa-angle-double-up"></i></a>
<a class="palette-button" id="palette-expand-all" href="#"><i class="fa fa-angle-double-down"></i></a>
</div>
</div><!-- /palette -->
<div id="workspace">
@@ -55,24 +59,21 @@
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart"></div>
<div id="workspace-toolbar">
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a>
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a>
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a>
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</a>
<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowName"><i class="fa fa-pencil"></i> </a>
<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>
<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>
<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>
</div>
<div id="workspace-footer">
<a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a>
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
</div>
</div>
<div id="chart-zoom-controls">
<div class="btn-group">
<a class="btn btn-mini" id="btn-zoom-out" href="#"><i class="fa fa-search-minus"></i></a>
<a class="btn btn-mini" id="btn-zoom-zero" href="#"><i class="fa fa-dot-circle-o"></i></a>
<a class="btn btn-mini" id="btn-zoom-in" href="#"><i class="fa fa-search-plus"></i></a>
</div>
</div>
<div id="sidebar">
<ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div>
<div id="sidebar-footer"></div>
</div>
<div id="sidebar-separator"></div>
@@ -80,14 +81,14 @@
</div>
<div id="notifications"></div>
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
<div id="subflow-dialog" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label>Name</label><input type="text" id="subflow-input-name">
<label data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div>
</form>
<div class="form-tips" id="subflow-dialog-user-count"></div>
@@ -95,26 +96,19 @@
<div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;">
Some of the nodes are not properly configured. Are you sure you want to deploy?
</div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;">
The workspace contains some unknown node types:
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm"> </div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
Are you sure you want to deploy?
</div>
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;">
The workspace contains some unused configuration nodes:
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unusedConfig;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unused-list"></ul>
Are you sure you want to deploy?
</div>
</form>
</div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
A <span id="node-dialog-library-save-type"></span> called <span id="node-dialog-library-save-name"></span> already exists. Overwrite?
<div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
</div>
</form>
</div>
@@ -122,12 +116,12 @@
<div id="node-dialog-library-save" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="node-dialog-library-save-folder"><i class="fa fa-folder-open"></i> Folder</label>
<input type="text" id="node-dialog-library-save-folder" placeholder="Folder">
<label for="node-dialog-library-save-folder" data-i18n="[append]library.folder"><i class="fa fa-folder-open"></i> </label>
<input type="text" id="node-dialog-library-save-folder" data-i18n="[placeholder]library.folderPlaceholder">
</div>
<div class="form-row">
<label for="node-dialog-library-save-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-dialog-library-save-filename" placeholder="Filename">
<label for="node-dialog-library-save-filename" data-i18n="[append]library.filename"><i class="fa fa-file"></i> </label>
<input type="text" id="node-dialog-library-save-filename" data-i18n="[placeholder]library.filenamePlaceholder">
</div>
</form>
</div>
@@ -136,7 +130,7 @@
<form class="form-horizontal">
<div class="form-row">
<ul id="node-dialog-library-breadcrumbs" class="breadcrumb">
<li class="active"><a href="#">Library</a></li>
<li class="active"><a href="#" data-i18n="[append]library.breadcrumb"></a></li>
</ul>
</div>
<div class="form-row">
@@ -152,30 +146,29 @@
<div id="node-dialog-rename-workspace" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="node-input-workspace-name" ><i class="fa fa-tag"></i> Name:</label>
<label for="node-input-workspace-name" ><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-workspace-name">
</div>
</form>
</div>
<div id="node-dialog-delete-workspace" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
Are you sure you want to delete '<span id="node-dialog-delete-workspace-name"></span>'?
<div style="text-align: center; padding-top: 30px;" id="node-dialog-delete-workspace-content">
</div>
</form>
</div>
<script type="text/x-red" data-template-name="export-library-dialog">
<div class="form-row">
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
<input type="text" id="node-input-filename" placeholder="Filename">
<label for="node-input-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>
<input type="text" id="node-input-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">
</div>
</script>
<script type="text/x-red" data-template-name="subflow">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>
<input type="text" id="node-input-name">
</div>
</script>

5038
editor/vendor/bootstrap/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

5
editor/vendor/i18next/i18next.min.js vendored Normal file

File diff suppressed because one or more lines are too long

197
locales/en-US/editor.json Normal file
View File

@@ -0,0 +1,197 @@
{
"common": {
"label": {
"name": "Name",
"ok": "Ok",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close"
}
},
"workspace": {
"defaultName": "Sheet __number__",
"renameSheet": "Rename sheet",
"confirmDelete": "Confirm delete",
"delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here"
},
"menu": {
"label": {
"sidebar": {
"sidebar": "Sidebar",
"show": "Toggle Sidebar"
},
"displayStatus": "Display Node Status",
"import": "Import",
"export": "Export",
"clipboard": "Clipboard",
"library": "Library",
"subflows": "Subflows",
"createSubflow": "Create Subflow",
"selectionToSubflow": "Selection to Subflow",
"workspaces": "Workspaces",
"add": "Add",
"rename": "Rename",
"delete": "Delete",
"keyboardShortcuts": "Keyboard Shortcuts",
"login": "Login",
"logout": "Logout"
}
},
"user": {
"loggedInAs": "Logged in as __name__",
"login": "Login",
"loginFailed": "Login failed"
},
"notification": {
"warning": "<strong>Warning</strong>: __message__",
"warnings": {
"undeployedChanges": "node has undeployed changes",
"nodeActionDisabled": "node actions disabled within subflow"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "Lost connection to server",
"cannotAddSubflowToItself": "Cannot add subflow to itself",
"cannotAddCircularReference": "Cannot add subflow - circular reference detected"
}
},
"clipboard": {
"nodes": "Nodes:",
"selectNodes": "Select the text above and copy to the clipboard.",
"pasteNodes": "Paste nodes here",
"importNodes": "Import nodes",
"exportNodes": "Export nodes to clipboard",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"invalidFlow": "Invalid flow: __message__"
},
"deploy": {
"deploy": "Deploy",
"full": "Full",
"fullDesc": "Deploys everything in the workspace",
"modifiedFlows": "Modified Flows",
"modifiedFlowsDesc": "Only deploys flows that contain changed nodes",
"modifiedNodes": "Modified Nodes",
"modifiedNodesDesc": "Only deploys nodes that have changed",
"successfulDeploy": "Successfully Deployed",
"errors": {
"noResponse": "no response from server"
},
"confirm": {
"button": {
"confirm": "Confirm deploy",
"cancel": "Cancel"
},
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
"improperlyConfigured": "Some of the nodes are not properly configured.",
"unknown": "The workspace contains some unknown node types:",
"unusedConfig": "The workspace contains some unused configuration nodes:",
"confirm": "Are you sure you want to deploy?"
}
},
"subflow": {
"tabLabel": "Subflow: __name__",
"editSubflow": "Edit flow __name__",
"edit": "Edit flow",
"subflowInstances": "There is __count__ instance of this subflow",
"subflowInstances_plural": "There are __count__ instances of this subflow",
"editSubflowName": "edit name",
"input": "input",
"output": "output",
"deleteSubflow": "delete subflow",
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
}
},
"editor": {
"configEdit": "edit",
"configAdd": "add",
"configDelete": "Delete",
"nodesUse": "__count__ node uses this config",
"nodesUse_plural": "__count__ nodes use this config",
"addNewConfig": "Add new __type__ config node",
"editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__..."
},
"keyboard": {
"selectAll": "Select all nodes",
"selectAllConnected": "Select all connected nodes",
"addRemoveNode": "Add/remove node from selection",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export selected nodes",
"toggleSidebar": "Toggle sidebar",
"deleteNode": "Delete selected nodes or link",
"copyNode": "Copy selected nodes",
"cutNode": "Cut selected nodes",
"pasteNode": "Paste nodes"
},
"library": {
"openLibrary": "Open Library...",
"saveToLibrary": "Save to Library...",
"typeLibrary": "__type__ library",
"unnamedType": "Unnamed __type__",
"saveToLibrary": "Save to Library",
"exportToLibrary": "Export nodes to library",
"dialogSaveOverwrite": "A __libraryType__ called __libraryName__ already exists. Overwrite?",
"invalidFilename": "Invalid filename",
"savedNodes": "Saved nodes",
"savedType": "Saved __type__",
"saveFailed": "Save failed: __message__",
"filename": "Filename",
"folder": "Folder",
"filenamePlaceholder": "file",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "Library"
},
"palette": {
"noInfo": "no information available",
"filter": "filter nodes",
"label": {
"subflows": "subflows",
"input": "input",
"output": "output",
"function": "function",
"social": "social",
"storage": "storage",
"analysis": "analysis",
"advanced": "advanced"
},
"event": {
"nodeAdded": "Node added to palette:",
"nodeAdded_plural": "Nodes added to palette",
"nodeRemoved": "Node removed from palette:",
"nodeRemoved_plural": "Nodes removed from palette:",
"nodeEnabled": "Node enabled:",
"nodeEnabled_plural": "Nodes enabled:",
"nodeDisabled": "Node disabled:",
"nodeDisabled_plural": "Nodes disabled:"
}
},
"sidebar": {
"info": {
"name": "Information",
"label": "info",
"node": "Node",
"type": "Type",
"id": "ID",
"subflow": "Subflow",
"instances": "Instances",
"properties": "Properties",
"blank": "blank",
"arrayItems": "__count__ items"
},
"config": {
"name": "Configuration nodes",
"label": "config"
}
}
}

123
locales/en-US/runtime.json Normal file
View File

@@ -0,0 +1,123 @@
{
"runtime": {
"welcome": "Welcome to Node-RED",
"version": "__component__ version: __version__",
"paths": {
"settings": "Settings file : __path__"
}
},
"server": {
"loading": "Loading palette nodes",
"errors": "Failed to register __count__ node type",
"errors_plural": "Failed to register __count__ node types",
"errors-help": "Run with -v for details",
"missing-modules": "Missing node modules:",
"removing-modules": "Removing modules from config",
"added-types": "Added node types:",
"removed-types": "Removed node types:",
"install": {
"invalid": "Invalid module name",
"installing": "Installing module: __name__",
"installed": "Installed module: __name__",
"install-failed": "Install failed",
"install-failed-long": "Installation of module __name__ failed:",
"install-failed-not-found": "$t(install-failed-long) module not found",
"uninstalling": "Uninstalling module: __name__",
"uninstall-failed": "Uninstall failed",
"uninstall-failed-long": "Uninstall of module __name__ failed:",
"uninstalled": "Uninstalled module: __name__"
},
"unable-to-listen": "Unable to listen on __listenpath__",
"port-in-use": "Error: port in use",
"uncaught-exception": "Uncaught Exception:",
"admin-ui-disabled": "Admin UI disabled",
"now-running": "Server now running at __listenpath__",
"failed-to-start": "Failed to start server:",
"headless-mode": "Running in headless mode",
"httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead"
},
"api": {
"flows": {
"error-save": "Error saving flows: __message__"
},
"library": {
"error-load-entry": "Error loading library entry '__path__': __message__",
"error-save-entry": "Error saving library entry '__path__': __message__",
"error-load-flow": "Error loading flow '__path__': __message__",
"error-save-flow": "Error saving flow '__path__': __message__"
},
"nodes": {
"enabled": "Enabled node types:",
"disabled": "Disabled node types:",
"error-enable": "Failed to enable node:"
}
},
"comms": {
"error": "Communication channel error: __message__",
"error-server": "Communication server error: __message__",
"error-send": "Communication send error: __message__"
},
"settings": {
"not-available": "Settings not available",
"property-read-only": "Property '__prop__' is read-only"
},
"nodes": {
"credentials": {
"error":"Error loading credentials: __message__",
"not-registered": "Credential type '__type__' is not registered"
},
"flows": {
"registered-missing": "Missing type registered: __type__",
"error": "Error loading flows: __message__",
"starting-modified-nodes": "Starting modified nodes",
"starting-modified-flows": "Starting modified flows",
"starting-flows": "Starting flows",
"started-modified-nodes": "Started modified nodes",
"started-modified-flows": "Started modified flows",
"started-flows": "Started flows",
"stopping-modified-nodes": "Stopping modified nodes",
"stopping-modified-flows": "Stopping modified flows",
"stopping-flows": "Stopping flows",
"stopped-modified-nodes": "Stopped modified nodes",
"stopped-modified-flows": "Stopped modified flows",
"stopped-flows": "Stopped flows",
"stopped": "Stopped",
"missing-types": "Waiting for missing types to be registered:",
"missing-type-provided": " - __type__ (provided by npm module __module__)",
"missing-type-install-1": "To install any of these missing modules, run:",
"missing-type-install-2": "in the directory:"
},
"flow": {
"unknown-type": "Unknown type: __type__",
"missing-types": "missing types",
"error-loop": "Message exceeded maximum number of catches"
},
"index": {
"unrecognised-id": "Unrecognised id: __id__",
"type-in-use": "Type in use: __msg__",
"unrecognised-module": "Unrecognised module: __module__"
},
"registry": {
"localfilesystem": {
"module-not-found": "Cannot find module '__module__'"
}
}
},
"storage": {
"index": {
"forbidden-flow-name": "forbidden flow name"
},
"localfilesystem": {
"user-dir": "User directory : __path__",
"flows-file": "Flows file : __path__",
"create": "Creating new flow file"
}
}
}

View File

@@ -16,8 +16,8 @@
<script type="text/x-red" data-template-name="sentiment">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>

View File

@@ -16,38 +16,48 @@
<script type="text/x-red" data-template-name="inject">
<div class="form-row node-input-payload">
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<select id="node-input-payloadType" style="width:73%">
<option value="date">timestamp</option>
<option value="string">string</option>
<option value="none">blank</option>
<option value="date" data-i18n="inject.timestamp"></option>
<option value="string" data-i18n="inject.string"></option>
<option value="none" data-i18n="inject.blank"></option>
</select>
</div>
<div class="form-row" id="node-input-row-payload">
<label for="node-input-payload"></label>
<input type="text" id="node-input-payload" placeholder="payload" style="width:70%">
<input type="text" id="node-input-payload" style="width:70%">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="topic" style="width: 70%x">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" style="width: 70%x">
</div>
<div class="form-row">
<label for=""><i class="fa fa-repeat"></i> Repeat</label>
<select id="inject-time-type-select" style="width: 73%"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
<input type="hidden" id="node-input-repeat" placeholder="payload">
<input type="hidden" id="node-input-crontab" placeholder="payload">
<label for=""><i class="fa fa-repeat"></i> <span data-i18n="inject.label.repeat"></span></label>
<select id="inject-time-type-select" style="width: 73%">
<option value="none" data-i18n="inject.none"></option>
<option value="interval" data-i18n="inject.interval"></option>
<option value="interval-time" data-i18n="inject.interval-time"></option>
<option value="time" data-i18n="inject.time"></option>
</select>
<input type="hidden" id="node-input-repeat">
<input type="hidden" id="node-input-crontab">
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
<span data-i18n="inject.every"></span>
<input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units">
<option value="s" data-i18n="inject.seconds"></option>
<option value="m" data-i18n="inject.minutes"></option>
<option value="h" data-i18n="inject.hours"></option>
</select><br/>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
<span data-i18n="inject.every"></span> <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
@@ -60,46 +70,46 @@
<option value="20">20</option>
<option value="30">30</option>
<option value="0">60</option>
</select> minutes<br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
</select> <span data-i18n="inject.minutes"></span><br/>
<span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
<div id="inject-time-interval-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display: inline-block; vertical-align: top;margin-right: 5px;" data-i18n="inject.on">on</div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> Monday</label>
<label><input type='checkbox' checked value='2'/> Tuesday</label>
<label><input type='checkbox' checked value='3'/> Wednesday</label>
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> Thursday</label>
<label><input type='checkbox' checked value='5'/> Friday</label>
<label><input type='checkbox' checked value='6'/> Saturday</label>
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> Sunday</label>
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
</div>
</div>
</div>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/>
<span data-i18n="inject.at"></span> <input id="inject-time-time" value="12:00"></input><br/>
<div id="inject-time-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> Monday</label>
<label><input type='checkbox' checked value='2'/> Tuesday</label>
<label><input type='checkbox' checked value='3'/> Wednesday</label>
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> Thursday</label>
<label><input type='checkbox' checked value='5'/> Friday</label>
<label><input type='checkbox' checked value='6'/> Saturday</label>
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> Sunday</label>
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
</div>
</div>
</div>
@@ -108,15 +118,15 @@
<div class="form-row" id="node-once">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
<label for="node-input-once" style="width: 70%;" data-i18n="inject.onstart"></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div>
<div class="form-tips" data-i18n="[html]inject.tip"></div>
</script>
<style>
.inject-time-row {
@@ -197,7 +207,7 @@
if ((this.topic.length < 24) && (this.topic.length > 0)) {
return this.name||this.topic;
}
else { return this.name||(this.payloadType==="date"?"timestamp":null)||"inject"; }
else { return this.name||(this.payloadType==="date"?this._("inject.timestamp"):null)||this._("inject.inject"); }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
@@ -474,24 +484,24 @@
button: {
onclick: function() {
var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
if (this.payloadType === "date") { label = "timestamp"; }
if (this.payloadType === "none") { label = "blank"; }
if (this.payloadType === "date") { label = this._("inject.timestamp"); }
if (this.payloadType === "none") { label = this._("inject.blank"); }
var node = this;
$.ajax({
url: "inject/"+this.id,
type:"POST",
success: function(resp) {
RED.notify("Successfully injected: "+label,"success");
RED.notify(node._("inject.success",{label:label}),"success");
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: inject node not deployed","error");
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.not-deployed")}),"error");
} else if (jqXHR.status == 500) {
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
RED.notify(node._("common.notification.error",{message:node._("inject.errors.failed")}),"error");
} else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.no-response")}),"error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+jqXHR.status+") "+textStatus,"error");
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:jqXHR.status,message:textStatus})}),"error");
}
}
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2014 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,12 +32,12 @@ module.exports = function(RED) {
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000;
if (RED.settings.verbose) { this.log("repeat = "+this.repeat); }
if (RED.settings.verbose) { this.log(RED._("inject.repeat",this)); }
this.interval_id = setInterval( function() {
node.emit("input",{});
}, this.repeat );
} else if (this.crontab) {
if (RED.settings.verbose) { this.log("crontab = "+this.crontab); }
if (RED.settings.verbose) { this.log(RED._("inject.crontab",this)); }
this.cronjob = new cron.CronJob(this.crontab,
function() {
node.emit("input",{});
@@ -68,10 +68,10 @@ module.exports = function(RED) {
InjectNode.prototype.close = function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log("inject: repeat stopped"); }
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
} else if (this.cronjob != null) {
this.cronjob.stop();
if (RED.settings.verbose) { this.log("inject: cronjob stopped"); }
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
delete this.cronjob;
}
}
@@ -84,7 +84,7 @@ module.exports = function(RED) {
res.send(200);
} catch(err) {
res.send(500);
node.error("Inject failed:"+err);
node.error(RED._("inject.failed",{error:err.toString()}));
}
} else {
res.send(404);

View File

@@ -16,8 +16,8 @@
<script type="text/x-red" data-template-name="catch">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="catch">
@@ -53,7 +53,7 @@
outputs:1,
icon: "alert.png",
label: function() {
return this.name||"catch";
return this.name||this._("catch.catch");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -16,25 +16,25 @@
<script type="text/x-red" data-template-name="debug">
<div class="form-row">
<label for="node-input-select-complete"><i class="fa fa-list"></i> Output</label>
<label for="node-input-select-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
<select type="text" id="node-input-select-complete" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">message property</option>
<option value="true">complete msg object</option>
<option value="false" data-i18n="debug.msgprop"></option>
<option value="true" data-i18n="debug.msgobj"></option>
</select>
</div>
<div class="form-row" id="node-prop-row">
<label for="node-input-complete">&nbsp;</label>msg.<input type="text" style="width:208px" id="node-input-complete">
</div>
<div class="form-row">
<label for="node-input-console"><i class="fa fa-random"></i> to</label>
<label for="node-input-console"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
<select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">debug tab</option>
<option value="true">debug tab and console</option>
<option value="false" data-i18n="debug.debtab"></option>
<option value="true" data-i18n="debug.tabcon"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -103,23 +103,24 @@
toggle: "active",
onclick: function() {
var label = this.name||"debug";
var node = this;
$.ajax({
url: "debug/"+this.id+"/"+(this.active?"enable":"disable"),
type: "POST",
success: function(resp, textStatus, xhr) {
if (xhr.status == 200) {
RED.notify("Successfully activated: "+label,"success");
RED.notify(node._("debug.notification.activated",{label:label}),"success");
} else if (xhr.status == 201) {
RED.notify("Successfully deactivated: "+label,"success");
RED.notify(node._("debug.notification.deactivated",{label:label}),"success");
}
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: debug node not deployed","error");
RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.not-deployed")}),"error");
} else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.no-response")}),"error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+") "+err.response,"error");
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:err.status,message:err.response})}),"error");
}
}
});
@@ -127,19 +128,23 @@
},
onpaletteadd: function() {
var content = document.createElement("div");
content.id = "tab-debug";
$(content).css({"position":"relative","height":"100%"});
var toolbar = document.createElement("div");
toolbar.id = "debug-toolbar";
content.appendChild(toolbar);
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
toolbar.innerHTML = '<div class="pull-right"><a id="debug-tab-clear" title="clear log" class="button" href="#"><i class="fa fa-trash"></i></a></div> ';
var messages = document.createElement("div");
messages.id = "debug-content";
content.appendChild(messages);
RED.sidebar.addTab("debug",content);
RED.sidebar.addTab({
id: "debug",
label: this._("debug.sidebar.label"),
name: this._("debug.sidebar.name"),
content: content
});
function getTimestamp() {
var d = new Date();
@@ -196,7 +201,7 @@
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'');
msg.innerHTML = '<span class="debug-message-date">'+
getTimestamp()+'</span>'+
(name?'<span class="debug-message-name">['+name+']':'')+
(name?'<span class="debug-message-name">'+name:'')+
'</span>';
// NOTE: relying on function error to have a "type" that all other msgs don't
if (o.hasOwnProperty("type") && (o.type === "function")) {
@@ -207,11 +212,11 @@
errorLvlType = 'warn';
}
msg.className = 'debug-message debug-message-level-' + errorLvl;
msg.innerHTML += '<span class="debug-message-topic">[function] : (' + errorLvlType + ')</span>';
msg.innerHTML += '<span class="debug-message-topic">function : (' + errorLvlType + ')</span>';
} else {
msg.innerHTML += '<span class="debug-message-topic">'+
(o.topic?topic+' : ':'')+
(o.property?'[msg.'+property+']':'[msg]')+" : "+format+
(o.property?'msg.'+property:'msg')+" : "+format+
'</span>';
}
@@ -279,7 +284,7 @@
.debug-message-topic {
display: block;
background: #fff;
padding: 1px 5px;
padding: 1px;
font-size: 10px;
color: #a66;
}

View File

@@ -82,13 +82,13 @@ module.exports = function(RED) {
msg.format = "error";
msg.msg = msg.msg.toString();
} else if (msg.msg instanceof Buffer) {
msg.format = "buffer";
msg.format = "buffer ["+msg.msg.length+"]";
msg.msg = msg.msg.toString('hex');
} else if (typeof msg.msg === 'object') {
var seen = [];
msg.format = "object";
if (util.isArray(msg.msg)) {
msg.format = "array";
msg.format = "array ["+msg.msg.length+"]";
}
msg.msg = JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
@@ -111,7 +111,7 @@ module.exports = function(RED) {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string";
msg.format = "string ["+msg.msg.length+"]";
msg.msg = msg.msg;
}

View File

@@ -16,28 +16,28 @@
<script type="text/x-red" data-template-name="exec">
<div class="form-row">
<label for="node-input-command"><i class="fa fa-file"></i> Command</label>
<input type="text" id="node-input-command" placeholder="command">
<label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label>
<input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command">
</div>
<div class="form-row">
<label><i class="fa fa-plus"></i> Append</label>
<label><i class="fa fa-plus"></i> <span data-i18n="exec.label.append"></span></label>
<input type="checkbox" id="node-input-addpay" style="display: inline-block; width: auto; vertical-align: top;">
&nbsp;msg.payload
</div>
<div class="form-row">
<label for="node-input-append"> </label>
<input type="text" id="node-input-append" placeholder="extra input parameters">
<input type="text" id="node-input-append" data-i18n="[placeholder]exec.placeholder.extraparams">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useSpawn" style="width: 70%;">Use spawn() instead of exec() ?</label>
<label for="node-input-useSpawn" style="width: 70%;"><span data-i18n="exec.spawn"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="spawnTip">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div>
<div class="form-tips" id="spawnTip"><span data-i18n="[html]exec.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="exec">

View File

@@ -37,7 +37,7 @@ module.exports = function(RED) {
if (typeof(msg.payload !== "string")) { msg.payload = (msg.payload || "").toString(); }
var arg = [];
if (node.append.length > 0) { arg = node.append.split(","); }
if ((node.addpay === true) && (msg.payload.trim() !== "")) { arg.unshift(msg.payload); }
if ((node.addpay === true) && (msg.payload.toString().trim() !== "")) { arg.unshift(msg.payload); }
if (RED.settings.verbose) { node.log(node.cmd+" ["+arg+"]"); }
if (node.cmd.indexOf(" ") == -1) {
var ex = spawn(node.cmd,arg);
@@ -63,11 +63,11 @@ module.exports = function(RED) {
node.error(code,msg);
});
}
else { node.error("Spawn command must be just the command - no spaces or extra parameters"); }
else { node.error(RED._("exec.spawnerr")); }
}
else {
var cl = node.cmd;
if ((node.addpay === true) && ((msg.payload || "").trim() !== "")) { cl += " "+msg.payload; }
if ((node.addpay === true) && ((msg.payload.toString() || "").trim() !== "")) { cl += " "+msg.payload; }
if (node.append.trim() !== "") { cl += " "+node.append; }
if (RED.settings.verbose) { node.log(cl); }
var child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
@@ -75,7 +75,7 @@ module.exports = function(RED) {
try {
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
} catch(e) {
node.log("Bad STDOUT");
node.log(RED._("exec.badstdout"));
}
var msg2 = {payload:stderr};
var msg3 = null;

View File

@@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="function">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
<label for="node-input-func"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
<input type="hidden" id="node-input-func" autofocus="autofocus">
<input type="hidden" id="node-input-noerr">
</div>
@@ -28,10 +28,10 @@
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div>
<div class="form-row">
<label for="node-input-outputs"><i class="fa fa-random"></i> Outputs</label>
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
<input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
</div>
<div class="form-tips">See the Info tab for help writing functions.</div>
<div class="form-tips"><span data-i18n="function.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="function">

View File

@@ -86,7 +86,8 @@ module.exports = function(RED) {
context: {
global:RED.settings.functionGlobalContext || {}
},
setTimeout: setTimeout
setTimeout: setTimeout,
clearTimeout: clearTimeout
};
var context = vm.createContext(sandbox);
try {
@@ -105,15 +106,27 @@ module.exports = function(RED) {
this.status({fill:"yellow",shape:"dot",text:""+converted});
}
} catch(err) {
var errorMessage = err.toString();
var line = 0;
var errorMessage;
var stack = err.stack.split(/\r?\n/);
if (stack.length > 0) {
var m = /at undefined:(\d+):(\d+)$/.exec(stack[1]);
if (m) {
var line = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+line+", col "+cha+")";
while(line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
}
if (line < stack.length) {
errorMessage = stack[line];
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
if (m) {
var line = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+line+", col "+cha+")";
}
}
}
if (!errorMessage) {
errorMessage = err.toString();
}
this.error(errorMessage, msg);
}

View File

@@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="template">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
<label for="node-input-template"><i class="fa fa-file-code-o"></i> <span data-i18n="template.label.template"></span></label>
<input type="hidden" id="node-input-template" autofocus="autofocus">
<select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;">
<option value="handlebars">mustache</option>
@@ -34,7 +34,7 @@
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
</div>
<div class="form-row">
<label for="node-input-field"><i class="fa fa-edit"></i> Property</label>
<label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label>
msg.<input type="text" id="node-input-field" placeholder="payload" style="width:170px;">
</div>
</script>

View File

@@ -18,58 +18,58 @@
<script type="text/x-red" data-template-name="delay">
<div class="form-row">
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> Action</label>
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
<select id="node-input-pauseType" style="width:270px !important">
<option value="delay">Delay message</option>
<option value="random">Random delay</option>
<option value="rate">Limit rate to</option>
<option value="queue">Topic based fair queue</option>
<option value="delay" data-i18n="delay.delaymsg"></option>
<option value="random" data-i18n="delay.ramdomdelay"></option>
<option value="rate" data-i18n="delay.limitrate"></option>
<option value="queue" data-i18n="delay.fairqueue"></option>
</select>
</div>
<div id="delay-details" class="form-row">
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> For</label>
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
<select id="node-input-timeoutUnits" style="width:200px !important">
<option value="milliseconds">Milliseconds</option>
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
<option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds" data-i18n="delay.secs"></option>
<option value="minutes" data-i18n="delay.mins"></option>
<option value="hours" data-i18n="delay.hours"></option>
<option value="days" data-i18n="delay.days"></option>
</select>
</div>
<div id="rate-details" class="form-row">
<label for="node-input-rate"><i class="fa fa-clock-o"></i> Rate</label>
<label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
<label for="node-input-rateUnits">msg(s) per</label>
<label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
<select id="node-input-rateUnits" style="width:140px !important">
<option value="second">Second</option>
<option value="minute">Minute</option>
<option value="hour">Hour</option>
<option value="day">Day</option>
<option value="second" data-i18n="delay.sec"></option>
<option value="minute" data-i18n="delay.min"></option>
<option value="hour" data-i18n="delay.hour"></option>
<option value="day" data-i18n="delay.day"></option>
</select>
<br/>
<div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label></div>
<div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop"><span data-i18n="delay.dropmsg"></span></label></div>
</div>
<div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> Between</label>
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
<label for="node-input-randomLast" style="width:20px"> &amp; </label>
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
<select id="node-input-randomUnits" style="width:140px !important">
<option value="milliseconds">Milliseconds</option>
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
<option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds" data-i18n="delay.secs"></option>
<option value="minutes" data-i18n="delay.mins"></option>
<option value="hours" data-i18n="delay.hours"></option>
<option value="days" data-i18n="delay.days"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -109,23 +109,23 @@
if (this.pauseType == "delay") {
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
return this.name||"delay "+this.timeout+" " + units;
return this.name||this._("delay.label.delay")+" "+this.timeout+" " + units;
} else if (this.pauseType == "rate") {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name||"limit "+this.rate+" msg/"+ units;
return this.name||this._("delay.label.limit")+" "+this.rate+" msg/"+ units;
} else if (this.pauseType == "random") {
return this.name || "random";
return this.name || this._("delay.label.random");
}
else {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name || "queue" +this.rate+" msg/"+ units;
return this.name || this._("delay.label.queue") +this.rate+" msg/"+ units;
}
},
labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-timeout" ).spinner({min:1,max:60});
$( "#node-input-timeout" ).spinner({min:1});
$( "#node-input-rate" ).spinner({min:1});
$( "#node-input-randomFirst" ).spinner({min:0});

View File

@@ -31,7 +31,7 @@ module.exports = function(RED) {
if (n.timeoutUnits === "milliseconds") {
this.timeout = n.timeout;
} else if (n.timeoutUnits === "minutes") {
} else if (n.timeoutUnits === "minutes") {
this.timeout = n.timeout * (60 * 1000);
} else if (n.timeoutUnits === "hours") {
this.timeout = n.timeout * (60 * 60 * 1000);
@@ -81,11 +81,15 @@ module.exports = function(RED) {
if (this.pauseType === "delay") {
this.on("input", function(msg) {
var id;
id = setTimeout(function(){
id = setTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
if (node.idList.length === 0) { node.status({}); }
node.send(msg);
}, node.timeout);
this.idList.push(id);
if ((node.timeout > 1000) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:" "});
}
});
this.on("close", function() {
@@ -93,6 +97,7 @@ module.exports = function(RED) {
clearTimeout(this.idList[i]);
}
this.idList = [];
this.status({});
});
} else if (this.pauseType === "rate") {
@@ -104,7 +109,7 @@ module.exports = function(RED) {
node.status({text:node.buffer.length});
}
if (node.buffer.length > 1000) {
node.warn(this.name + " buffer exceeded 1000 messages");
node.warn(this.name + " " + RED._("delay.error.buffer"));
}
} else {
node.send(msg);
@@ -112,7 +117,7 @@ module.exports = function(RED) {
if (node.buffer.length === 0) {
clearInterval(node.intervalID);
node.intervalID = -1;
node.status({text:""});
node.status({});
}
if (node.buffer.length > 0) {
@@ -165,13 +170,13 @@ module.exports = function(RED) {
this.on("close", function() {
clearInterval(this.intervalID);
this.buffer = [];
node.status({text:node.buffer.length});
node.status({});
});
} else if (this.pauseType === "random") {
this.on("input", function(msg) {
var wait = node.randomFirst + (node.diff * Math.random());
var id = setTimeout(function(){
var id = setTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
node.send(msg);
}, wait);

View File

@@ -16,48 +16,48 @@
<script type="text/x-red" data-template-name="trigger">
<div class="form-row">
Send
<span data-i18n="trigger.send"></span>
<select id="node-input-op1type" style="width:200px !important">
<option value="val">the string payload</option>
<option value="pay">the existing message</option>
<option value="nul">nothing</option>
<option value="val" data-i18n="trigger.output.string"></option>
<option value="pay" data-i18n="trigger.output.existing"></option>
<option value="nul" data-i18n="trigger.output.nothing"></option>
</select>
<input style="width: 180px !important" type="text" id="node-input-op1">
</div>
<div class="form-row">
then
<span data-i18n="trigger.then"></span>
<select id="node-then-type" style="width:150px;">
<option value="block">wait to be reset</option>
<option value="wait">wait for</option>
<option value="block" data-i18n="trigger.wait-reset"></option>
<option value="wait" data-i18n="trigger.wait-for"></option>
</select>
<span class="node-type-wait">
<input type="text" id="node-input-duration" style="width:70px !important">
<input type="text" id="node-input-duration" style="text-align:right; width:70px !important">
<select id="node-input-units" style="width:140px !important">
<option value="ms">Milliseconds</option>
<option value="s">Seconds</option>
<option value="min">Minutes</option>
<option value="hr">Hours</option>
<option value="ms" data-i18n="trigger.duration.ms"></option>
<option value="s" data-i18n="trigger.duration.s"></option>
<option value="min" data-i18n="trigger.duration.m"></option>
<option value="hr" data-i18n="trigger.duration.h"></option>
</select>
</span>
</div>
<div class="form-row node-type-wait">
then send
<span data-i18n="trigger.then-send"></span>
<select id="node-input-op2type" style="width:200px !important">
<option value="val">the string payload</option>
<option value="pay">the existing message</option>
<option value="nul">nothing</option>
<option value="val" data-i18n="trigger.output.string"></option>
<option value="pay" data-i18n="trigger.output.existing"></option>
<option value="nul" data-i18n="trigger.output.nothing"></option>
</select>
<input style="width: 145px !important" type="text" id="node-input-op2">
</div>
<div class="form-row node-type-wait">
<input type="checkbox" id="node-input-extend" style="margin-left: 5px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend">extend delay if new message arrives</label>
<input type="checkbox" id="node-input-extend" style="margin-left: 5px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips">The node can be reset by sending a message with the <b>msg.reset</b> property set</div>
<div class="form-tips" data-i18n="[html]trigger.tip"></div>
</script>
<script type="text/x-red" data-help-name="trigger">
@@ -95,10 +95,10 @@
icon: "trigger.png",
label: function() {
if (this.duration > 0) {
return this.name|| "trigger"+" "+this.duration+this.units;
return this.name|| this._("trigger.label.trigger")+" "+this.duration+this.units;
}
else {
return this.name|| "trigger & block";
return this.name|| this._("trigger.label.trigger-block");
}
},
labelStyle: function() {
@@ -136,19 +136,19 @@
$("#node-then-type").change();
$("#node-input-op1type").change();
$("#node-input-op2type").change();
if (this.extend === "true" || this.extend === true) {
$("#node-input-extend").prop("checked",true);
} else {
$("#node-input-extend").prop("checked",false);
}
},
oneditsave: function() {
if ($("#node-then-type").val() == "block") {
$("#node-input-duration").val("0");
}
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* Copyright 2014, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@ module.exports = function(RED) {
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
else { msg.payload = node.op1; }
if (node.op1type !== "nul") { node.send(msg); }
if (node.duration === 0) { tout = "infinite"; }
if (node.duration === 0) { tout = 0; }
else {
tout = setTimeout(function() {
msg.payload = m2;
@@ -73,7 +73,7 @@ module.exports = function(RED) {
},node.duration);
}
}
else if ((node.extend == "true") && (node.duration > 0)) {
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
clearTimeout(tout);
tout = setTimeout(function() {
msg.payload = m2;

View File

@@ -16,17 +16,17 @@
<script type="text/x-red" data-template-name="comment">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-comment"></i> Title</label>
<input type="text" id="node-input-name" placeholder="Comment">
<label for="node-input-name"><i class="fa fa-comment"></i> <span data-i18n="comment.label.title"></span></label>
<input type="text" id="node-input-name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> Body - will be rendered in info tab.</label>
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> <span data-i18n="comment.label.body"></span></label>
<input type="hidden" id="node-input-info" autofocus="autofocus">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor" ></div>
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor"></div>
</div>
<div class="form-tips">Tip: The text here can be styled as <i><a href="https://help.github.com/articles/markdown-basics/" target="_new">Github flavoured Markdown</a></i></div>
<div class="form-tips" data-i18n="[html]comment.tip"></div>
</script>
<script type="text/x-red" data-help-name="comment">
@@ -51,9 +51,7 @@
return this.name?"node_label_italic":"";
},
info: function() {
var t = this.name || "Comment node";
var b = this.info || "Use this node to add simple documentation.\n\nAnything you add will be rendered in this info panel.\n\nYou may use Markdown syntax to **enhance** the *presentation*.";
return "### "+t+"\n"+b;
return (this.name?"# "+this.name+"\n":"")+(this.info||"");
},
oneditprepare: function() {
var that = this;

View File

@@ -15,10 +15,7 @@
-->
<script type="text/x-red" data-template-name="unknown">
<div class="form-tips"><p>This node is a type unknown to your installation of Node-RED.</p>
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
the flow will not start until the missing type is installed.</i></p>
<p>See the Info side bar for more help</p></div>
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="unknown">
@@ -42,7 +39,7 @@
outputs:1,
icon: "",
label: function() {
return "("+this.name+")"||"unknown";
return "("+this.name+")"||this._("unknown.label.unknown");
},
labelStyle: function() {
return "node_label_unknown";

View File

@@ -16,49 +16,49 @@
<script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
<label for="node-input-pin"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.gpiopin"></span></label>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option>
<option value="3">3 - SDA1 </option>
<option value="5">5 - SCL1 </option>
<option value="7">7 - GPIO7</option>
<option value="8">8 - TxD </option>
<option value="10">10 - RxD </option>
<option value="11">11 - GPIO0</option>
<option value="12">12 - GPIO1</option>
<option value="13">13 - GPIO2</option>
<option value="15">15 - GPIO3</option>
<option value="16">16 - GPIO4</option>
<option value="18">18 - GPIO5</option>
<option value="19">19 - MOSI </option>
<option value="21">21 - MISO </option>
<option value="22">22 - GPIO6</option>
<option value="23">23 - SCLK </option>
<option value="24">24 - CE0 </option>
<option value="26">26 - CE1 </option>
<option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="3">3 - SDA1 - BCM2</option>
<option value="5">5 - SCL1 - BCM3</option>
<option value="7">7 - GPIO7 - BCM4</option>
<option value="8">8 - TxD - BCM14</option>
<option value="10">10 - RxD - BCM15</option>
<option value="11">11 - GPIO0 - BCM17</option>
<option value="12">12 - GPIO1 - BCM18</option>
<option value="13">13 - GPIO2 - BCM27</option>
<option value="15">15 - GPIO3 - BCM22</option>
<option value="16">16 - GPIO4 - BCM23</option>
<option value="18">18 - GPIO5 - BCM24</option>
<option value="19">19 - MOSI - BCM10</option>
<option value="21">21 - MISO - BCM9</option>
<option value="22">22 - GPIO6 - BCM25</option>
<option value="23">23 - SCLK - BCM11</option>
<option value="24">24 - CE0 - BCM8</option>
<option value="26">26 - CE1 - BCM7</option>
</select>
&nbsp;<span id="pitype"></span>
</div>
<div class="form-row">
<label for="node-input-intype"><i class="fa fa-level-up"></i> Resistor ?</label>
<label for="node-input-intype"><i class="fa fa-level-up"></i> <span data-i18n="rpi-gpio.label.resistor"></span></label>
<select type="text" id="node-input-intype" style="width: 150px;">
<option value="tri">none</option>
<option value="up">pullup</option>
<option value="down">pulldown</option>
<option value="tri" data-i18n="rpi-gpio.resistor.none"></option>
<option value="up" data-i18n="rpi-gpio.resistor.pullup"></option>
<option value="down" data-i18n="rpi-gpio.resistor.pulldown"></option>
</select>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-read" style="width: 70%;">Read initial state of pin on deploy/restart ?</label>
<label for="node-input-read" style="width: 70%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
<div class="form-tips">Tip: Only Digital Input is supported - input must be 0 or 1.</div>
<div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
<div class="form-tips"><span data-i18n="[html]rpi-gpio.tip.in"></span></div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio in">
@@ -97,34 +97,38 @@
},
oneditprepare: function() {
var pinnow = this.pin;
var pintip = this._("rpi-gpio.tip.pin");
var pinname = this._("rpi-gpio.pinname");
var alreadyuse = this._("rpi-gpio.alreadyuse");
var alreadyset = this._("rpi-gpio.alreadyset");
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) {
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO5"));
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO6"));
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO12"));
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO13"));
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO19"));
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO16"));
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO26"));
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO20"));
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO21"));
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0 - BCM0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0 - BCM1"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21 - BCM5"));
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22 - BCM6"));
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26 - BCM12"));
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23 - BCM13"));
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24 - BCM19"));
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27 - BCM16"));
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25 - BCM26"));
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28 - BCM20"));
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29 - BCM21"));
$('#node-input-pin').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
$('#pin-tip').html(pintip + Object.keys(data));
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn");
RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
}
pinnow = pinnew;
}
@@ -133,7 +137,7 @@
$("#node-input-intype").change(function() {
var newtype = $("#node-input-intype option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
RED.notify(pinname+" "+pinnow+" "+alreadyset+" "+pinsInUse[pinnow],"error");
}
});
}
@@ -142,56 +146,56 @@
<script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
<label for="node-input-pin"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.gpiopin"></span></label>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option>
<option value="3">3 - SDA1 </option>
<option value="5">5 - SCL1 </option>
<option value="7">7 - GPIO7</option>
<option value="8">8 - TxD </option>
<option value="10">10 - RxD </option>
<option value="11">11 - GPIO0</option>
<option value="12">12 - GPIO1</option>
<option value="13">13 - GPIO2</option>
<option value="15">15 - GPIO3</option>
<option value="16">16 - GPIO4</option>
<option value="18">18 - GPIO5</option>
<option value="19">19 - MOSI </option>
<option value="21">21 - MISO </option>
<option value="22">22 - GPIO6</option>
<option value="23">23 - SCLK </option>
<option value="24">24 - CE0 </option>
<option value="26">26 - CE1 </option>
<option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="3">3 - SDA1 - BCM2</option>
<option value="5">5 - SCL1 - BCM3</option>
<option value="7">7 - GPIO7 - BCM4</option>
<option value="8">8 - TxD - BCM14</option>
<option value="10">10 - RxD - BCM15</option>
<option value="11">11 - GPIO0 - BCM17</option>
<option value="12">12 - GPIO1 - BCM18</option>
<option value="13">13 - GPIO2 - BCM27</option>
<option value="15">15 - GPIO3 - BCM22</option>
<option value="16">16 - GPIO4 - BCM23</option>
<option value="18">18 - GPIO5 - BCM24</option>
<option value="19">19 - MOSI - BCM10</option>
<option value="21">21 - MISO - BCM9</option>
<option value="22">22 - GPIO6 - BCM25</option>
<option value="23">23 - SCLK - BCM11</option>
<option value="24">24 - CE0 - BCM8</option>
<option value="26">26 - CE1 - BCM7</option>
</select>
&nbsp;<span id="pitype"></span>
</div>
<div class="form-row" id="node-set-pwm">
<label>&nbsp;&nbsp;&nbsp;&nbsp;Type</label>
<label>&nbsp;&nbsp;&nbsp;&nbsp;<span data-i18n="rpi-gpio.label.type"></span></label>
<select id="node-input-out" style="width: 250px;">
<option value="out">Digital output</option>
<option value="pwm">PWM output</option>
<option value="out" data-i18n="rpi-gpio.digout"></option>
<option value="pwm" data-i18n="rpi-gpio.pwmout"></option>
</select>
</div>
<div class="form-row" id="node-set-tick">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-set" style="width: 70%;">Initialise pin state ?</label>
<label for="node-input-set" style="width: 70%;"><span data-i18n="rpi-gpio.label.initpin"></span></label>
</div>
<div class="form-row" id="node-set-state">
<label for="node-input-level">&nbsp;</label>
<select id="node-input-level" style="width: 250px;">
<option value="0">initial level of pin - low (0)</option>
<option value="1">initial level of pin - high (1)</option>
<option value="0" data-i18n="rpi-gpio.initpin0"></option>
<option value="1" data-i18n="rpi-gpio.initpin1"></option>
</select>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
<div class="form-tips" id="dig-tip"><b>Tip</b>: For digital output - input must be 0 or 1.</div>
<div class="form-tips" id="pwm-tip"><b>Tip</b>: For PWM output - input must be between 0 to 100.</div>
<div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
<div class="form-tips" id="dig-tip"><span data-i18n="[html]rpi-gpio.tip.dig"></span></div>
<div class="form-tips" id="pwm-tip"><span data-i18n="[html]rpi-gpio.tip.pwm"></span></div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
@@ -234,35 +238,39 @@
},
oneditprepare: function() {
var pinnow = this.pin;
var pintip = this._("rpi-gpio.tip.pin");
var pinname = this._("rpi-gpio.pinname");
var alreadyuse = this._("rpi-gpio.alreadyuse");
var alreadyset = this._("rpi-gpio.alreadyset");
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) {
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO5"));
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO6"));
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO12"));
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO13"));
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO19"));
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO16"));
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO26"));
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO20"));
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO21"));
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0 - BCM0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0 - BCM1"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21 - BCM5"));
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22 - BCM6"));
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26 - BCM12"));
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23 - BCM13"));
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24 - BCM19"));
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27 - BCM16"));
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25 - BCM26"));
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28 - BCM20"));
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29 - BCM21"));
$('#node-input-pin').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
$('#pin-tip').html(pintip + Object.keys(data));
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn");
RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
}
pinnow = pinnew;
}
@@ -271,7 +279,7 @@
$("#node-input-out").change(function() {
var newtype = $("#node-input-out option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
RED.notify(pinname+" "+pinnow+" "+alreadyset+" "+pinsInUse[pinnow],"error");
}
});
@@ -307,17 +315,17 @@
<script type="text/x-red" data-template-name="rpi-mouse">
<div class="form-row">
<label for="node-input-butt"><i class="fa fa-circle"></i> Button</label>
<label for="node-input-butt"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.button"></span></label>
<select type="text" id="node-input-butt" style="width: 250px;">
<option value="1">left</option>
<option value="2">right</option>
<option value="4">middle</option>
<option value="7">any</option>
<option value="1" data-i18n="rpi-gpio.left"></option>
<option value="2" data-i18n="rpi-gpio.right"></option>
<option value="4" data-i18n="rpi-gpio.middle"></option>
<option value="7" data-i18n="rpi-gpio.any"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -341,10 +349,10 @@
outputs:1,
icon: "rpi.png",
label: function() {
var na = "Pi Mouse";
if (this.butt === "1") { na += " Left"; }
if (this.butt === "2") { na += " Right"; }
if (this.butt === "4") { na += " Middle"; }
var na = this._("rpi-gpio.label.pimouse");
if (this.butt === "1") { na += " "+this._("rpi-gpio.label.left"); }
if (this.butt === "2") { na += " "+this._("rpi-gpio.label.right"); }
if (this.butt === "4") { na += " "+this._("rpi-gpio.label.middle"); }
return this.name||na;
},
labelStyle: function() {

View File

@@ -23,25 +23,25 @@ module.exports = function(RED) {
var gpioCommand = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
//RED.log.info("Ignoring Raspberry Pi specific node.");
throw "Info : Ignoring Raspberry Pi specific node.";
//RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
}
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
RED.log.warn("Can't find Pi RPi.GPIO python library.");
throw "Warning : Can't find Pi RPi.GPIO python library.";
RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
}
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
RED.log.error(gpioCommand+" needs to be executable.");
throw "Error : nrgpio must to be executable.";
RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
}
// the magic to make python print stuff immediately
process.env.PYTHONUNBUFFERED = 1;
var pinsInUse = {};
var pinTypes = {"out":"digital output", "tri":"input", "up":"input with pull up", "down":"input with pull down", "pwm":"PWM output"};
var pinTypes = {"out":RED._("rpi-gpio.types.digout"), "tri":RED._("rpi-gpio.types.input"), "up":RED._("rpi-gpio.types.pullup"), "down":RED._("rpi-gpio.types.pulldown"), "pwm":RED._("rpi-gpio.types.pwmout")};
function GPIOInNode(n) {
RED.nodes.createNode(this,n);
@@ -56,7 +56,7 @@ module.exports = function(RED) {
}
else {
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
}
}
@@ -67,7 +67,7 @@ module.exports = function(RED) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
data = data.toString().trim();
@@ -88,27 +88,27 @@ module.exports = function(RED) {
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
else { node.error('error: ' + err.errno); }
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
});
}
else {
node.warn("Invalid GPIO pin: "+node.pin);
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
@@ -133,7 +133,7 @@ module.exports = function(RED) {
}
else {
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
}
}
@@ -150,11 +150,11 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
}
else {
node.error("nrpgio python command not running",msg);
node.status({fill:"red",shape:"ring",text:"not running"});
node.error(RED._("rpi-gpio.errors.pythoncommandnotfound"),msg);
node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.not-running"});
}
}
else { node.warn("Invalid input: "+out); }
else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
}
if (node.pin !== undefined) {
@@ -164,7 +164,7 @@ module.exports = function(RED) {
node.child = spawn(gpioCommand, [node.out,node.pin]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.on("input", inputlistener);
@@ -179,27 +179,27 @@ module.exports = function(RED) {
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
else { node.error('error: ' + err.errno); }
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
}
else {
node.warn("Invalid GPIO pin: "+node.pin);
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
@@ -214,14 +214,14 @@ module.exports = function(RED) {
var pitype = { type:"" };
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
if (err) {
RED.log.info('Version command failed for some reason.');
RED.log.info(RED._("rpi-gpio.errors.version"));
}
else {
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
else { RED.log.info("Saw Pi Type",stdout.trim()); }
else { RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); }
}
});
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
@@ -232,7 +232,7 @@ module.exports = function(RED) {
var node = this;
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"OK"});
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
data = Number(data);
@@ -247,22 +247,22 @@ module.exports = function(RED) {
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
else if (err.errno === "EACCES") { node.error('nrgpio ommand not executable'); }
else { node.error('error: ' + err.errno); }
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');

View File

@@ -16,16 +16,16 @@
<script type="text/x-red" data-template-name="mqtt in">
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -58,32 +58,32 @@
<script type="text/x-red" data-template-name="mqtt out">
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div>
<div class="form-row">
<label for="node-input-qos"><i class="fa fa-empire"></i> QoS</label>
<label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-input-qos" style="width:125px !important">
<option value=""></option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;Retain &nbsp;<select id="node-input-retain" style="width:125px !important">
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-input-retain" style="width:125px !important">
<option value=""></option>
<option value="false">false</option>
<option value="true">true</option>
<option value="false" data-i18n="mqtt.false"></option>
<option value="true" data-i18n="mqtt.true"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips">Tip: Leave topic, qos or retain blank if you want to set them via msg properties.</div>
<div class="form-tips"><span data-i18n="mqtt.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="mqtt out">
@@ -118,21 +118,21 @@
<script type="text/x-red" data-template-name="mqtt-broker">
<div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> Broker</label>
<label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> <span data-i18n="mqtt.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
</div>
<div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> Client ID</label>
<input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
<input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
</div>
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
<label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-config-input-user">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-config-input-password">
</div>
</script>

View File

@@ -42,7 +42,7 @@ module.exports = function(RED) {
this.broker = n.broker;
this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"});
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this;
if (this.topic) {
@@ -55,22 +55,22 @@ module.exports = function(RED) {
node.send(msg);
}, this.id);
this.client.on("connectionlost",function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
});
this.client.on("connect",function() {
node.status({fill:"green",shape:"dot",text:"connected"});
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
if (this.client.isConnected()) {
node.status({fill:"green",shape:"dot",text:"connected"});
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
} else {
this.client.connect();
}
}
else {
this.error("topic not defined");
this.error(RED._("mqtt.errors.not-defined"));
}
} else {
this.error("missing broker configuration");
this.error(RED._("mqtt.errors.missing-config"));
}
this.on('close', function() {
if (this.client) {
@@ -90,7 +90,7 @@ module.exports = function(RED) {
this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"});
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this;
this.on("input",function(msg) {
@@ -110,22 +110,22 @@ module.exports = function(RED) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
this.client.publish(msg); // send the message
}
else { node.warn("Invalid topic specified"); }
else { node.warn(RED._("mqtt.errors.invalid-topic")); }
}
});
this.client.on("connectionlost",function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
});
this.client.on("connect",function() {
node.status({fill:"green",shape:"dot",text:"connected"});
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
if (this.client.isConnected()) {
node.status({fill:"green",shape:"dot",text:"connected"});
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
} else {
this.client.connect();
}
} else {
this.error("missing broker configuration");
this.error(RED._("mqtt.errors.missing-config"));
}
this.on('close', function() {
if (this.client) {

View File

@@ -16,7 +16,7 @@
<script type="text/x-red" data-template-name="http in">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:72%;">
<option value="get">GET</option>
<option value="post">POST</option>
@@ -25,18 +25,18 @@
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> url</label>
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="/url">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row row-swagger-doc">
<label for="node-input-name"><i class="fa fa-tag"></i> Doc</label>
<label for="node-input-swaggerDoc"><i class="fa fa-tag"></i> <span data-i18n="httpin.label.doc"></span></label>
<input type="text" id="node-input-swaggerDoc">
</div>
<div id="node-input-tip" class="form-tips">The url will be relative to <code><span id="node-input-path"></span></code>.</div>
<div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>
</script>
<script type="text/x-red" data-help-name="http in">
@@ -67,10 +67,10 @@
<script type="text/x-red" data-template-name="http response">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips">The messages sent to this node <b>must</b> originate from an <i>http input</i> node</div>
<div class="form-tips"><span data-i18n="[html]httpin.tip.res"></span></div>
</script>
<script type="text/x-red" data-help-name="http response">
@@ -86,45 +86,45 @@
<script type="text/x-red" data-template-name="http request">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:72%;">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="use">- set by msg.method -</option>
<option value="use" data-i18n="httpin.setby"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> URL</label>
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="http://">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useAuth" style="width: 70%;">Use basic authentication ?</label>
<label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label>
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> Username</label>
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user">
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-input-password">
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> Return</label>
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:72%;">
<option value="txt">a UTF-8 string</option>
<option value="bin">a binary buffer</option>
<option value="obj">a parsed JSON object</option>
<option value="txt" data-i18n="httpin.utf8"></option>
<option value="bin" data-i18n="httpin.binary"></option>
<option value="obj" data-i18n="httpin.json"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="tip-json" hidden>Tip: If the JSON parse fails the fetched string is returned as-is.</div>
<div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
</script>
<script type="text/x-red" data-help-name="http request">
@@ -160,7 +160,7 @@
name: {value:""},
url: {value:"",required:true},
method: {value:"get",required:true},
swaggerDoc: {type:"swagger-doc", exclusive:true, required:false}
swaggerDoc: {type:"swagger-doc", required:false}
},
inputs:0,
outputs:1,
@@ -241,7 +241,7 @@
outputs:1,
icon: "white-globe.png",
label: function() {
return this.name||"http request";
return this.name||this._("httpin.httpreq");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -137,7 +137,7 @@ module.exports = function(RED) {
}
});
} else {
this.warn("Cannot create http-in node when httpNodeRoot set to false");
this.warn(RED._("httpin.errors.not-created"));
}
}
RED.nodes.registerType("http in",HTTPIn);
@@ -172,7 +172,7 @@ module.exports = function(RED) {
msg.res.send(statusCode,msg.payload);
}
} else {
node.warn("No response object");
node.warn(RED._("httpin.errors.no-response"));
}
});
}
@@ -195,16 +195,16 @@ module.exports = function(RED) {
this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"requesting"});
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
var url = nodeUrl || msg.url;
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
node.warn(RED._("common.errors.nooverride"));
}
if (isTemplatedUrl) {
url = mustache.render(nodeUrl,msg);
}
if (!url) {
node.error("No url specified",msg);
node.error(RED._("httpin.errors.no-url"),msg);
return;
}
// url must start http:// or https:// so assume http:// if not set
@@ -214,7 +214,7 @@ module.exports = function(RED) {
var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
node.warn("Warning: msg properties can no longer override fixed node properties. Use explicit override option. See bit.ly/nr-override-msg-props");
node.warn(RED._("common.errors.nooverride"));
}
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
@@ -312,7 +312,7 @@ module.exports = function(RED) {
}
else if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); }
catch(e) { node.warn("JSON parse error"); }
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
}
node.send(msg);
node.status({});

View File

@@ -1,279 +1,276 @@
<!--
Copyright 2013 IBM Corp.
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.
-->
<script type="text/javascript">
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
</script>
<!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket input node.</p>
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
The socket can be configured to expect a properly formed JSON string, in which
case it will parse the JSON and send on the resulting object as the entire message.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket in',{
category: 'input',
defaults: {
name: {value:""},
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:0,
outputs:1,
icon: "white-globe.png",
labelStyle: function() {
return this.name?"node_label_italic":"";
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
</script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p>
<p>If the message arriving at this node started at a WebSocket In node, the message
will be sent back to the client that triggered the flow. Otherwise, the message
will be broadcast to all connected clients.</p>
<p>If you want to broadcast a message that started at a WebSocket In node, you
should delete the <b>msg._session</b> property within the flow</p>.
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket out',{
category: 'output',
defaults: {
name: {value:""},
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:1,
outputs:0,
icon: "white-globe.png",
align: "right",
labelStyle: function() {
return this.name?"node_label_italic":"";
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
</script>
<!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-config-input-path" placeholder="/ws/example">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option>
</select>
</div>
<div class="form-tips">
By default, <code>payload</code> 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.
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
</div>
</script>
<script type="text/x-red" data-help-name="websocket-listener">
<p>This configuration node creates a WebSocket Server using the specified path</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-listener',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) != "/") {
root = root+"/";
}
if (this.path.charAt(0) == "/") {
root += this.path.slice(1);
} else {
root += this.path;
}
return root;
},
oneditprepare: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) == "/") {
root = root.slice(0,-1);
}
if (root == "") {
$("#node-config-ws-tip").hide();
} else {
$("#node-config-ws-path").html(root);
$("#node-config-ws-tip").show();
}
}
});
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option>
</select>
</div>
<div class="form-tips">
<p>URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.</p>
By default, <code>payload</code> 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.
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-client',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
return this.path;
}
});
</script>
<!--
Copyright 2013 IBM Corp.
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.
-->
<!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode">
<option value="server" data-i18n="websocket.listenon"></option>
<option value="client" data-i18n="websocket.connectto"></option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket input node.</p>
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
The socket can be configured to expect a properly formed JSON string, in which
case it will parse the JSON and send on the resulting object as the entire message.</p>
</script>
<script type="text/javascript">
(function() {
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
RED.nodes.registerType('websocket in',{
category: 'input',
defaults: {
name: {value:""},
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:0,
outputs:1,
icon: "white-globe.png",
labelStyle: function() {
return this.name?"node_label_italic":"";
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
RED.nodes.registerType('websocket out',{
category: 'output',
defaults: {
name: {value:""},
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:1,
outputs:0,
icon: "white-globe.png",
align: "right",
labelStyle: function() {
return this.name?"node_label_italic":"";
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
RED.nodes.registerType('websocket-listener',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) != "/") {
root = root+"/";
}
if (this.path.charAt(0) == "/") {
root += this.path.slice(1);
} else {
root += this.path;
}
return root;
},
oneditprepare: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) == "/") {
root = root.slice(0,-1);
}
if (root == "") {
$("#node-config-ws-tip").hide();
} else {
$("#node-config-ws-path").html(root);
$("#node-config-ws-tip").show();
}
}
});
RED.nodes.registerType('websocket-client',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
return this.path;
}
});
})();
</script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode">
<option value="server" data-i18n="websocket.listenon"></option>
<option value="client" data-i18n="websocket.connectto"></option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p>
<p>If the message arriving at this node started at a WebSocket In node, the message
will be sent back to the client that triggered the flow. Otherwise, the message
will be broadcast to all connected clients.</p>
<p>If you want to broadcast a message that started at a WebSocket In node, you
should delete the <b>msg._session</b> property within the flow</p>.
</script>
<!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input type="text" id="node-config-input-path" placeholder="/ws/example">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false" data-i18n="websocket.payload"></option>
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-tips">
<span data-i18n="[html]websocket.tip.path1"></span>
<p id="node-config-ws-tip"><span data-i18n="[html]websocket.tip.path2"></span><code><span id="node-config-ws-path"></span></code>.</p>
</div>
</script>
<script type="text/x-red" data-help-name="websocket-listener">
<p>This configuration node creates a WebSocket Server using the specified path</p>
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false" data-i18n="websocket.payload"></option>
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-tips">
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
<span data-i18n="[html]websocket.tip.url2"></span>
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>

View File

@@ -1,232 +1,234 @@
/**
* Copyright 2013,2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var ws = require("ws");
var inspect = require("sys").inspect;
// A node red node that sets up a local websocket server
function WebSocketListenerNode(n) {
// Create a RED node
RED.nodes.createNode(this,n);
var node = this;
// Store local copies of the node configuration (as defined in the .html)
node.path = n.path;
node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events
node._clients = {};
// match absolute url
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
node.closing = false;
function startconn() { // Connect to remote endpoint
var socket = new ws(node.path);
node.server = socket; // keep for closing
handleConnection(socket);
}
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); }
socket.on('open',function() {
if (!node.isServer) { node.emit('opened',''); }
});
socket.on('close',function() {
if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); }
else { node.emit('closed'); }
if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
}
});
socket.on('message',function(data,flags){
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
node.emit('erro');
if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
}
});
}
if (node.isServer) {
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server
node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener){
if(event == "error" || event == "upgrade" || event == "listening"){
node._serverListeners[event] = listener;
}
}
RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path});
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events
RED.server.removeListener('newListener',storeListener);
node.server.on('connection', handleConnection);
}
else {
node.closing = false;
startconn(); // start outbound connection
}
node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253
// Remove listeners from RED.server
if (node.isServer) {
var listener = null;
for (var event in node._serverListeners) {
if (node._serverListeners.hasOwnProperty(event)) {
listener = node._serverListeners[event];
if (typeof listener === "function") {
RED.server.removeListener(event,listener);
}
}
}
node._serverListeners = {};
node.server.close();
node._inputNodes = [];
}
else {
node.closing = true;
node.server.close();
if (node.tout) { clearTimeout(tout); }
}
});
}
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
this._inputNodes.push(handler);
}
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
var msg;
if (this.wholemsg) {
try {
msg = JSON.parse(data);
}
catch(err) {
msg = { payload:data };
}
} else {
msg = {
payload:data
};
}
msg._session = {type:"websocket",id:id};
for (var i = 0; i < this._inputNodes.length; i++) {
this._inputNodes[i].send(msg);
}
}
WebSocketListenerNode.prototype.broadcast = function(data) {
try {
if(this.isServer) {
for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data);
}
}
else {
this.server.send(data);
}
}
catch(e) { // swallow any errors
this.warn("ws:"+i+" : "+e);
}
}
WebSocketListenerNode.prototype.reply = function(id,data) {
var session = this._clients[id];
if (session) {
try {
session.send(data);
}
catch(e) { // swallow any errors
}
}
}
function WebSocketInNode(n) {
RED.nodes.createNode(this,n);
this.server = (n.client)?n.client:n.server;
var node = this;
this.serverConfig = RED.nodes.getNode(this.server);
if (this.serverConfig) {
this.serverConfig.registerInputNode(this);
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
} else {
this.error("Missing server configuration");
}
}
RED.nodes.registerType("websocket in",WebSocketInNode);
function WebSocketOutNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) {
this.error("Missing server configuration");
}
else {
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
}
this.on("input", function(msg) {
var payload;
if (this.serverConfig.wholemsg) {
delete msg._session;
payload = JSON.stringify(msg);
} else if (msg.hasOwnProperty("payload")) {
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
payload = RED.util.ensureString(msg.payload);
}
else {
payload = msg.payload;
}
}
if (payload) {
if (msg._session && msg._session.type == "websocket") {
node.serverConfig.reply(msg._session.id,payload);
} else {
node.serverConfig.broadcast(payload,function(error){
if (!!error) {
node.warn("An error occurred while sending:" + inspect(error));
}
});
}
}
});
}
RED.nodes.registerType("websocket out",WebSocketOutNode);
}
/**
* Copyright 2013,2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var ws = require("ws");
var inspect = require("sys").inspect;
// A node red node that sets up a local websocket server
function WebSocketListenerNode(n) {
// Create a RED node
RED.nodes.createNode(this,n);
var node = this;
// Store local copies of the node configuration (as defined in the .html)
node.path = n.path;
node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events
node._clients = {};
// match absolute url
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
node.closing = false;
function startconn() { // Connect to remote endpoint
var socket = new ws(node.path);
node.server = socket; // keep for closing
handleConnection(socket);
}
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); }
socket.on('open',function() {
if (!node.isServer) { node.emit('opened',''); }
});
socket.on('close',function() {
if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); }
else { node.emit('closed'); }
if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
}
});
socket.on('message',function(data,flags){
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
node.emit('erro');
if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
}
});
}
if (node.isServer) {
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server
node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener){
if(event == "error" || event == "upgrade" || event == "listening"){
node._serverListeners[event] = listener;
}
}
RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path});
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events
RED.server.removeListener('newListener',storeListener);
node.server.on('connection', handleConnection);
}
else {
node.closing = false;
startconn(); // start outbound connection
}
node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253
// Remove listeners from RED.server
if (node.isServer) {
var listener = null;
for (var event in node._serverListeners) {
if (node._serverListeners.hasOwnProperty(event)) {
listener = node._serverListeners[event];
if (typeof listener === "function") {
RED.server.removeListener(event,listener);
}
}
}
node._serverListeners = {};
node.server.close();
node._inputNodes = [];
}
else {
node.closing = true;
node.server.close();
if (node.tout) { clearTimeout(tout); }
}
});
}
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
this._inputNodes.push(handler);
}
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
var msg;
if (this.wholemsg) {
try {
msg = JSON.parse(data);
}
catch(err) {
msg = { payload:data };
}
} else {
msg = {
payload:data
};
}
msg._session = {type:"websocket",id:id};
for (var i = 0; i < this._inputNodes.length; i++) {
this._inputNodes[i].send(msg);
}
}
WebSocketListenerNode.prototype.broadcast = function(data) {
try {
if(this.isServer) {
for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data);
}
}
else {
this.server.send(data);
}
}
catch(e) { // swallow any errors
this.warn("ws:"+i+" : "+e);
}
}
WebSocketListenerNode.prototype.reply = function(id,data) {
var session = this._clients[id];
if (session) {
try {
session.send(data);
}
catch(e) { // swallow any errors
}
}
}
function WebSocketInNode(n) {
RED.nodes.createNode(this,n);
this.server = (n.client)?n.client:n.server;
var node = this;
this.serverConfig = RED.nodes.getNode(this.server);
if (this.serverConfig) {
this.serverConfig.registerInputNode(this);
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
} else {
this.error(RED._("websocket.errors.missing-conf"));
}
}
RED.nodes.registerType("websocket in",WebSocketInNode);
function WebSocketOutNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) {
this.error(RED._("websocket.errors.missing-conf"));
}
else {
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
}
this.on("input", function(msg) {
var payload;
if (this.serverConfig.wholemsg) {
delete msg._session;
payload = JSON.stringify(msg);
} else if (msg.hasOwnProperty("payload")) {
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
payload = RED.util.ensureString(msg.payload);
}
else {
payload = msg.payload;
}
}
if (payload) {
if (msg._session && msg._session.type == "websocket") {
node.serverConfig.reply(msg._session.id,payload);
} else {
node.serverConfig.broadcast(payload,function(error){
if (!!error) {
node.warn(RED._("websocket.errors.send-error")+inspect(error));
}
});
}
}
});
}
RED.nodes.registerType("websocket out",WebSocketOutNode);
}

View File

@@ -16,14 +16,14 @@
<script type="text/x-red" data-template-name="watch">
<div class="form-row node-input-filename">
<label for="node-input-files"><i class="fa fa-file"></i> File(s)</label>
<input type="text" id="node-input-files" placeholder="File(s) or Directory">
<label for="node-input-files"><i class="fa fa-file"></i> <span data-i18n="watch.label.files"></span></label>
<input type="text" id="node-input-files" data-i18n="[placeholder]watch.placeholder.files">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div id="node-input-tip" class="form-tips">On Windows you must use double back-slashes \\ in any directory names.</div>
<div id="node-input-tip" class="form-tips"><span data-i18n="watch.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="watch">

View File

@@ -16,43 +16,42 @@
<script type="text/x-red" data-template-name="tcp in">
<div class="form-row">
<label for="node-input-server"><i class="fa fa-dot-circle-o"></i> Type</label>
<label for="node-input-server"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
<select id="node-input-server" style="width:120px; margin-right:5px;">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
<option value="server" data-i18n="tcpin.type.listen"></option>
<option value="client" data-i18n="tcpin.type.connect"></option>
</select>
port <input type="text" id="node-input-port" style="width: 50px">
<span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width: 50px">
</div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
</div>
<div class="form-row">
<label><i class="fa fa-sign-out"></i> Output</label>
a
<label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
<select id="node-input-datamode" style="width:110px;">
<option value="stream">stream of</option>
<option value="single">single</option>
<option value="stream" data-i18n="tcpin.output.stream"></option>
<option value="single" data-i18n="tcpin.output.single"></option>
</select>
<select id="node-input-datatype" style="width:140px;">
<option value="buffer">Buffer</option>
<option value="utf8">String</option>
<option value="base64">Base64 String</option>
<option value="buffer" data-i18n="tcpin.output.buffer"></option>
<option value="utf8" data-i18n="tcpin.output.string"></option>
<option value="base64" data-i18n="tcpin.output.base64"></option>
</select>
payload<span id="node-input-datamode-plural">s</span>
<span data-i18n="tcpin.label.payload"></span>
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left: 110px;">
delimited by <input type="text" id="node-input-newline" style="width: 110px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width: 110px;">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -96,14 +95,12 @@
var datamode = $("#node-input-datamode option:selected").val();
var datatype = $("#node-input-datatype option:selected").val();
if (datamode == "stream") {
$("#node-input-datamode-plural").show();
if (datatype == "utf8") {
$("#node-row-newline").show();
} else {
$("#node-row-newline").hide();
}
} else {
$("#node-input-datamode-plural").hide();
$("#node-row-newline").hide();
}
};
@@ -118,41 +115,34 @@
<script type="text/x-red" data-template-name="tcp out">
<div class="form-row">
<label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> Type</label>
<label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
<select id="node-input-beserver" style="width:150px; margin-right:5px;">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
<option value="reply">Reply to TCP</option>
<option value="server" data-i18n="tcpin.type.listen"></option>
<option value="client" data-i18n="tcpin.type.connect"></option>
<option value="reply" data-i18n="tcpin.type.reply"></option>
</select>
<span id="node-input-port-row">port <input type="text" id="node-input-port" style="width: 50px"></span>
<span id="node-input-port-row"><span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width: 50px"></span>
</div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;">
</div>
<div class="form-row hidden" id="node-input-end-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-end" style="width: 70%;">Close connection after each message is sent ?</label>
<label for="node-input-end" style="width: 70%;"><span data-i18n="tcpin.label.close-connection"></span></label>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 message ?</label>
<label for="node-input-base64" style="width: 70%;"><span data-i18n="tcpin.label.decode-base64"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips hidden" id="fin-tip">
<b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example.
</div>
<div class="form-tips hidden" id="fin-tip2">
<b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example. The receiving client will need to reconnect.
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -201,17 +191,8 @@
if (sockettype == "client") {
$("#node-input-host-row").show();
$("#fin-tip").show();
} else {
$("#node-input-host-row").hide();
$("#fin-tip").hide();
}
if (sockettype == "server") {
$("#fin-tip2").show();
}
else {
$("#fin-tip2").hide();
}
};
updateOptions();
@@ -223,27 +204,25 @@
<script type="text/x-red" data-template-name="tcp request">
<div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
<label for="node-input-server"><i class="fa fa-globe"></i> <span data-i18n="tcpin.label.server"></span></label>
<input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">
&nbsp;port <input type="text" id="node-input-port" placeholder="number" style="width:60px">
&nbsp;port <input type="text" id="node-input-port" style="width:60px">
</div>
<div class="form-row">
<label for="node-input-out"><i class="fa fa-sign-out"></i> Return</label>
<label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
<select type="text" id="node-input-out" style="width:56%;">
<option value="time">after a fixed timeout of</option>
<option value="char">when character received is</option>
<option value="count">a fixed number of characters</option>
<option value="sit">never. Keep connection open</option>
<option value="time" data-i18n="tcpin.return.timeout"></option>
<option value="char" data-i18n="tcpin.return.character"></option>
<option value="count" data-i18n="tcpin.return.number"></option>
<option value="sit" data-i18n="tcpin.return.never"></option>
</select>
<input type="text" id="node-input-splitc" style="width:50px;">
<span id="node-units"></span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips"><b>Tip:</b> Outputs a binary <b>Buffer</b>, so you may want to .toString() it.</br/>
<b>Tip:</b> Leave host and port blank if you want to overide with msg.host and msg.port properties.</div>
<script>
var previous = null;
$("#node-input-out").on('focus', function () { previous = this.value; }).change(function() {

View File

@@ -43,14 +43,14 @@ module.exports = function(RED) {
var reconnectTimeout;
var end = false;
var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port);
node.status({fill:"grey",shape:"dot",text:"connecting"});
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
var id = (1+Math.random()*4294967295).toString(16);
client = net.connect(node.port, node.host, function() {
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
node.connected = true;
node.log("connected to "+node.host+":"+node.port);
node.status({fill:"green",shape:"dot",text:"connected"});
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
connectionPool[id] = client;
@@ -96,14 +96,14 @@ module.exports = function(RED) {
client.on('close', function() {
delete connectionPool[id];
node.connected = false;
node.status({fill:"red",shape:"ring",text:"disconnected"});
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
if (!node.closing) {
if (end) { // if we were asked to close then try to reconnect once very quick.
end = false;
reconnectTimeout = setTimeout(setupTcpClient, 20);
}
else {
node.log("connection lost to "+node.host+":"+node.port);
node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
}
} else {
@@ -128,7 +128,7 @@ module.exports = function(RED) {
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = (1+Math.random()*4294967295).toString(16);
connectionPool[id] = socket;
node.status({text:++count+" connections"});
node.status({text:RED._("tcpin.status.connections",{count:++count})});
var buffer = (node.datatype == 'buffer')? new Buffer(0):"";
socket.on('data', function (data) {
@@ -170,12 +170,12 @@ module.exports = function(RED) {
}
});
socket.on('timeout', function() {
node.log('timeout closed socket port '+node.port);
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
socket.end();
});
socket.on('close', function() {
delete connectionPool[id];
node.status({text:--count+" connections"});
node.status({text:RED._("tcpin.status.connections",{count:--count})});
});
socket.on('error',function(err) {
node.log(err);
@@ -183,15 +183,15 @@ module.exports = function(RED) {
});
server.on('error', function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
}
});
server.listen(node.port, function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
} else {
node.log('listening on port '+node.port);
node.log(RED._("tcpin.status.listening-port",{port:node.port}));
node.on('close', function() {
for (var c in connectionPool) {
if (connectionPool.hasOwnProperty(c)) {
@@ -201,7 +201,7 @@ module.exports = function(RED) {
}
node.closing = true;
server.close();
node.log('stopped listening on port '+node.port);
node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
});
}
});
@@ -228,20 +228,20 @@ module.exports = function(RED) {
var end = false;
var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port);
node.status({fill:"grey",shape:"dot",text:"connecting"});
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
client = net.connect(node.port, node.host, function() {
node.connected = true;
node.log("connected to "+node.host+":"+node.port);
node.status({fill:"green",shape:"dot",text:"connected"});
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
client.on('error', function (err) {
node.log(err);
node.log(RED._("tcpin.errors.error",{error:err.toString()}));
});
client.on('end', function (err) {
});
client.on('close', function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
node.connected = false;
client.destroy();
if (!node.closing) {
@@ -250,7 +250,7 @@ module.exports = function(RED) {
reconnectTimeout = setTimeout(setupTcpClient,20);
}
else {
node.log("connection lost to "+node.host+":"+node.port);
node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
}
} else {
@@ -301,26 +301,26 @@ module.exports = function(RED) {
});
} else {
var connectedSockets = [];
node.status({text:"0 connections"});
node.status({text:RED._("tcpin.status.connections",{count:0})});
var server = net.createServer(function (socket) {
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
node.log("connection from "+remoteDetails);
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.push(socket);
node.status({text:connectedSockets.length+" connections"});
node.status({text:connectedSockets.length+" "+"tcpin.status.connections"});
socket.on('timeout', function() {
node.log('timeout closed socket port '+node.port);
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
socket.end();
});
socket.on('close',function() {
node.log("connection closed from "+remoteDetails);
node.log(RED._("tcpin.status.connection-closed",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:connectedSockets.length+" connections"});
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
});
socket.on('error',function() {
node.log("socket error from "+remoteDetails);
node.log(RED._("tcpin.errors.socket-error",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:connectedSockets.length+" connections"});
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
});
});
@@ -343,15 +343,15 @@ module.exports = function(RED) {
server.on('error', function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
}
});
server.listen(node.port, function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
} else {
node.log('listening on port '+node.port);
node.log(RED._("tcpin.status.listening-port",{port:node.port}));
node.on('close', function() {
for (var c in connectedSockets) {
if (connectedSockets.hasOwnProperty(c)) {
@@ -360,7 +360,7 @@ module.exports = function(RED) {
}
}
server.close();
node.log('stopped listening on port '+node.port);
node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
});
}
});
@@ -393,7 +393,7 @@ module.exports = function(RED) {
}
if (!node.connected) {
client = net.Socket();
client.setTimeout(socketTimeout);
if (socketTimeout) { client.setTimeout(socketTimeout); }
//node.status({});
var host = node.server || msg.host;
var port = node.port || msg.port;
@@ -401,18 +401,17 @@ module.exports = function(RED) {
if (host && port) {
client.connect(port, host, function() {
//node.log('client connected');
node.status({fill:"green",shape:"dot",text:"connected"});
//node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.connected = true;
client.write(msg.payload);
});
}
else {
node.warn("Host and/or port not set");
node.warn(RED._("tcpin.errors.no-host"));
}
client.on('data', function(data) {
//node.log("data:"+ data.length+":"+ data);
if (node.out == "sit") { // if we are staying connected just send the buffer
node.send({"payload": data});
}
@@ -433,7 +432,7 @@ module.exports = function(RED) {
m = new Buffer(i+1);
buf.copy(m,0,0,i+1);
node.send({"payload":m});
if (client) { client.end(); }
//if (client) { client.end(); }
}, node.splitc);
i = 0;
buf[0] = data[j];
@@ -446,7 +445,8 @@ module.exports = function(RED) {
if ( i >= node.splitc) {
m = new Buffer(i);
buf.copy(m,0,0,i);
if (client) { client.end(); }
node.send({"payload":m});
//if (client) { client.end(); }
i = 0;
}
}
@@ -457,7 +457,8 @@ module.exports = function(RED) {
if (data[j] == node.splitc) {
m = new Buffer(i);
buf.copy(m,0,0,i);
if (client) { client.end(); }
node.send({"payload":m});
//if (client) { client.end(); }
i = 0;
}
}
@@ -473,22 +474,22 @@ module.exports = function(RED) {
});
client.on('close', function() {
node.status({});
if (node.done) { node.done(); }
});
client.on('error', function() {
node.error('connect failed',msg);
node.status({fill:"red",shape:"ring",text:"error"});
node.error(RED._("tcpin.errors.connect-fail"),msg);
node.status({fill:"red",shape:"ring",text:"common.status.error"});
if (client) { client.end(); }
});
client.on('timeout',function() {
node.warn('connect timeout');
node.warn(RED._("tcpin.errors.connect-timeout"));
if (client) {
client.end();
setTimeout(function() {
client.connect(port, host, function() {
//node.log('client connected');
node.connected = true;
client.write(msg.payload);
});
@@ -503,7 +504,7 @@ module.exports = function(RED) {
node.done = done;
if (client) {
buf = null;
client.end();
client.destroy();
}
if (!node.connected) { done(); }
});

View File

@@ -17,54 +17,41 @@
<!-- The Input Node -->
<script type="text/x-red" data-template-name="udp in">
<div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> Listen for</label>
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.listen"></span></label>
<select id="node-input-multicast" style='width:62%'>
<option value="false">udp messages</option>
<option value="true">multicast messages</option>
<option value="false" data-i18n="udp.udpmsgs"></option>
<option value="true" data-i18n="udp.mcmsgs"></option>
</select>
</div>
<div class="form-row node-input-group">
<label for="node-input-group"><i class="fa fa-list"></i> Group</label>
<label for="node-input-group"><i class="fa fa-list"></i> <span data-i18n="udp.label.group"></span></label>
<input type="text" id="node-input-group" placeholder="225.0.18.83">
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interface">
</div>
<div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> on Port</label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 80px">
&nbsp;&nbsp;using <select id="node-input-ipv" style="width:80px">
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>
<input type="text" id="node-input-port" style="width: 80px">
&nbsp;&nbsp;<span data-i18n="udp.label.using"></span> <select id="node-input-ipv" style="width:80px">
<option value="udp4">ipv4</option>
<option value="udp6">ipv6</option>
</select>
</div>
<div class="form-row">
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> Output</label>
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="udp.label.output"></span></label>
<select id="node-input-datatype" style="width: 70%;">
<option value="buffer">a Buffer</option>
<option value="utf8">a String</option>
<option value="base64">a Base64 encoded string</option>
<option value="buffer" data-i18n="udp.output.buffer"></option>
<option value="utf8" data-i18n="udp.output.string"></option>
<option value="base64" data-i18n="udp.output.base64"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips">Tip: Make sure your firewall will allow the data in.</div>
<script>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id == "false") {
$(".node-input-group").hide();
$(".node-input-iface").hide();
}
else {
$(".node-input-group").show();
$(".node-input-iface").show();
}
});
</script>
<div class="form-tips"><span data-i18n="udp.tip.in"></span></div>
</script>
<script type="text/x-red" data-help-name="udp in">
@@ -97,6 +84,20 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id == "false") {
$(".node-input-group").hide();
$(".node-input-iface").hide();
}
else {
$(".node-input-group").show();
$(".node-input-iface").show();
}
});
$("#node-input-multicast").change();
}
});
</script>
@@ -105,62 +106,44 @@
<!-- The Output Node -->
<script type="text/x-red" data-template-name="udp out">
<div class="form-row">
<label for="node-input-port"><i class="fa fa-envelope"></i> Send a</label>
<label for="node-input-port"><i class="fa fa-envelope"></i> <span data-i18n="udp.label.send"></span></label>
<select id="node-input-multicast" style="width:40%">
<option value="false">udp message</option>
<option value="broad">broadcast message</option>
<option value="multi">multicast message</option>
<option value="false" data-i18n="udp.udpmsg"></option>
<option value="broad" data-i18n="udp.bcmsg"></option>
<option value="multi" data-i18n="udp.mcmsg"></option>
</select>
to port <input type="text" id="node-input-port" placeholder="port" style="width: 70px">
<span data-i18n="udp.label.toport"></span> <input type="text" id="node-input-port" style="width: 70px">
</div>
<div class="form-row node-input-addr">
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> Address</label>
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 50%;">
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> <span data-i18n="udp.label.address"></span></label>
<input type="text" id="node-input-addr" data-i18n="[placeholder]udp.placeholder.address" style="width: 50%;">
<select id="node-input-ipv" style="width:70px">
<option value="udp4">ipv4</option>
<option value="udp6">ipv6</option>
</select>
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interface">
</div>
<div class="form-row">
<label for="node-input-outport-type">&nbsp;</label>
<select id="node-input-outport-type">
<option id="node-input-outport-type-random" value="random">use random local port</option>
<option value="fixed">bind to local port</option>
<option id="node-input-outport-type-random" value="random" data-i18n="udp.bind.random"></option>
<option value="fixed" data-i18n="udp.bind.local"></option>
</select>
<input type="text" id="node-input-outport" style="width: 70px;" placeholder="port">
<input type="text" id="node-input-outport" style="width: 70px;">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 encoded payload ?</label>
<label for="node-input-base64" style="width: 70%;"><span data-i18n="udp.label.decode-base64"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips">Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>.</div>
<script>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id !== "multi") {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Address');
$("#node-input-addr")[0].placeholder = 'destination ip';
}
else {
$(".node-input-iface").show();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Group');
$("#node-input-addr")[0].placeholder = '225.0.18.83';
}
if (id === "broad") {
$("#node-input-addr")[0].placeholder = '255.255.255.255';
}
});
</script>
<div class="form-tips"><span data-i18n="[html]udp.tip.out"></span></div>
</script>
<script type="text/x-red" data-help-name="udp out">
@@ -195,6 +178,12 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var addresslabel = this._("udp.label.address");
var addressph = this._("udp.placeholder.address");
var grouplabel = this._("udp.label.group");
var bindrandom = this._("udp.bind.random");
var bindtarget = this._("udp.bind.target");
var type = this.outport==""?"random":"fixed";
$("#node-input-outport-type option").filter(function() {
return $(this).val() == type;
@@ -208,15 +197,30 @@
$("#node-input-outport").show();
}
});
$("#node-input-outport-type").change();
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id === "multi") {
$(".node-input-iface").show();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + grouplabel);
$("#node-input-addr")[0].placeholder = '225.0.18.83';
}
else if (id === "broad") {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
$("#node-input-addr")[0].placeholder = '255.255.255.255';
}
else {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
$("#node-input-addr")[0].placeholder = addressph;
}
var type = $(this).children("option:selected").val();
if (type == "false") {
$("#node-input-outport-type-random").html("bind to random local port");
$("#node-input-outport-type-random").html(bindrandom);
} else {
$("#node-input-outport-type-random").html("bind to target port");
$("#node-input-outport-type-random").html(bindtarget);
}
});
$("#node-input-multicast").change();

View File

@@ -33,9 +33,9 @@ module.exports = function(RED) {
server.on("error", function (err) {
if ((err.code == "EACCES") && (node.port < 1024)) {
node.error("UDP access error, you may need root access for ports below 1024");
node.error(RED._("udp.errors.access-error"));
} else {
node.error("UDP error : "+err.code);
node.error(RED._("udp.errors.error",{error:err.code}));
}
server.close();
});
@@ -54,20 +54,20 @@ module.exports = function(RED) {
server.on('listening', function () {
var address = server.address();
node.log('udp listener at ' + address.address + ":" + address.port);
node.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
if (node.multicast == "true") {
server.setBroadcast(true);
try {
server.setMulticastTTL(128);
server.addMembership(node.group,node.iface);
node.log("udp multicast group "+node.group);
node.log(RED._("udp.status.mc-group",{group:node.group}));
} catch (e) {
if (e.errno == "EINVAL") {
node.error("Bad Multicast Address");
node.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") {
node.error("Must be ip address of the required interface");
node.error(RED._("udp.errors.interface"));
} else {
node.error("Error :"+e.errno);
node.error(RED._("udp.errors.error",{error:e.errno}));
}
}
}
@@ -76,7 +76,7 @@ module.exports = function(RED) {
node.on("close", function() {
try {
server.close();
node.log('udp listener stopped');
node.log(RED._("udp.status.listener-stopped"));
} catch (err) {
node.error(err);
}
@@ -118,25 +118,25 @@ module.exports = function(RED) {
try {
sock.setMulticastTTL(128);
sock.addMembership(node.addr,node.iface); // Add to the multicast group
node.log('udp multicast ready : '+node.outport+' -> '+node.addr+":"+node.port);
node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port}));
} catch (e) {
if (e.errno == "EINVAL") {
node.error("Bad Multicast Address");
node.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") {
node.error("Must be ip address of the required interface");
node.error(RED._("udp.errors.interface"));
} else {
node.error("Error :"+e.errno);
node.error(RED._("udp.errors.error",{error:e.errno}));
}
}
} else {
node.log('udp broadcast ready : '+node.outport+' -> '+node.addr+":"+node.port);
node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port}));
}
});
} else if (node.outport != "") {
sock.bind(node.outport);
node.log('udp ready : '+node.outport+' -> '+node.addr+":"+node.port);
node.log(RED._("udp.errors.ready",{outport:node.outport,host:node.addr,port:node.port}));
} else {
node.log('udp ready : '+node.addr+":"+node.port);
node.log(RED._("udp.errors.ready-nolocal",{host:node.addr,port:node.port}));
}
node.on("input", function(msg) {
@@ -144,11 +144,11 @@ module.exports = function(RED) {
var add = node.addr || msg.ip || "";
var por = node.port || msg.port || 0;
if (add == "") {
node.warn("udp: ip address not set");
node.warn(RED._("udp.errors.ip-notset"));
} else if (por == 0) {
node.warn("udp: port not set");
node.warn(RED._("udp.errors.port-notset"));
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
node.warn("udp: port number not valid");
node.warn(RED._("udp.errors.port-invalid"));
} else {
var message;
if (node.base64) {
@@ -171,7 +171,7 @@ module.exports = function(RED) {
node.on("close", function() {
try {
sock.close();
node.log('udp output stopped');
node.log(RED._("udp.status.output-stopped"));
} catch (err) {
node.error(err);
}

View File

@@ -0,0 +1,611 @@
{
"common": {
"label": {
"payload": "Payload",
"topic": "Topic",
"name": "Name",
"username": "Username",
"password": "Password"
},
"status": {
"connected": "connected",
"not-connected": "not connected",
"disconnected": "disconnected",
"connecting": "connecting",
"error": "error",
"ok": "OK"
},
"notification": {
"error": "<strong>Error</strong>: __message__",
"errors": {
"not-deployed": "node not deployed",
"no-response": "no response from server",
"unexpected": "unexpected error (__status__) __message__"
}
},
"errors": {
"nooverride": "Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props"
}
},
"inject": {
"inject": "inject",
"repeat": "repeat = __repeat__",
"crontab": "crontab = __crontab__",
"stopped": "stopped",
"failed": "Inject failed: __error__",
"label": {
"repeat": "Repeat"
},
"timestamp": "timestamp",
"string": "string",
"blank": "blank",
"none": "none",
"interval": "interval",
"interval-time": "interval between times",
"time": "at a specific time",
"seconds": "seconds",
"minutes": "minutes",
"hours": "hours",
"between": "between",
"at": "at",
"and": "and",
"every": "every",
"days": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
],
"on": "on",
"onstart": "Inject once at start?",
"tip": "<b>Note:</b> \"interval between times\" and \"at a specific time\" will use cron.<br/>See info box for details.",
"success": "Successfully injected: __label__",
"errors": {
"failed": "inject failed, see log for details"
}
},
"catch": {
"catch": "catch"
},
"debug": {
"output": "Output",
"msgprop": "message property",
"msgobj": "complete msg object",
"to": "to",
"debtab": "debug tab",
"tabcon": "debug tab and console",
"notification": {
"activated": "Successfully activated: __label__",
"deactivated": "Successfully deactivated: __label__"
},
"sidebar": {
"label": "debug",
"name": "Debug messages"
}
},
"exec": {
"spawnerr": "Spawn command must be just the command - no spaces or extra parameters",
"badstdout": "Bad STDOUT",
"label": {
"command": "Command",
"append": "Append"
},
"placeholder": {
"extraparams": "extra input parameters"
},
"spawn": "Use spawn() instead of exec()?",
"tip": "Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated."
},
"function": {
"label": {
"function": "Function",
"outputs": "Outputs"
},
"tip": "See the Info tab for help writing functions."
},
"template": {
"label": {
"template": "Template",
"property": "Property"
},
"templatevalue": "This is the payload: {{payload}} !"
},
"delay": {
"action": "Action",
"for": "For",
"delaymsg": "Delay message",
"ramdomdelay": "Random delay",
"limitrate": "Limit rate to",
"fairqueue": "Topic based fair queue",
"milisecs": "Miliseconds",
"secs": "Seconds",
"sec": "Second",
"mins": "Minutes",
"min": "Minute",
"hours": "Hours",
"hour": "Hour",
"days": "Days",
"day": "Day",
"between": "Between",
"rate": "Rate",
"msgper": "msg(s) per",
"dropmsg": "drop intermediate messages",
"label": {
"delay": "delay",
"limit": "limit",
"random": "ramdom",
"queue": "queue"
},
"error": {
"buffer": "buffer exceeded 1000 messages"
}
},
"trigger": {
"send": "Send",
"then": "then",
"then-send": "then send",
"output": {
"string": "the string payload",
"existing": "the existing message",
"nothing": "nothing"
},
"wait-reset": "wait to be reset",
"wait-for": "wait for",
"duration": {
"ms": "Milliseconds",
"s": "Seconds",
"m": "Minutes",
"h": "Hours"
},
"extend": "extend delay if new message arrives",
"tip": "The node can be reset by sending a message with the <b>msg.reset</b> property set",
"label": {
"trigger": "trigger",
"trigger-block": "trigger & block"
}
},
"comment": {
"label": {
"title": "Title",
"body": "Body"
},
"tip": "Tip: The text can be styled as <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_new\">Github flavoured Markdown</a>"
},
"unknown": {
"label": {
"unknown": "unknown"
},
"tip": "<p>This node is a type unknown to your installation of Node-RED.</p><p><i>If you deploy with the node in this state, it's configuration will be preserved, but the flow will not start until the missing type is installed.</i></p><p>See the Info side bar for more help</p>"
},
"mqtt": {
"label": {
"broker": "Broker",
"qos": "QoS",
"clientid": "Client ID",
"port": "Port"
},
"placeholder": {
"clientid": "Leave blank for auto generated"
},
"retain": "Retain",
"true": "true",
"false": "false",
"tip": "Tip: Leave topic, qos or retain blank if you want to set them via msg properties.",
"errors": {
"not-defined": "topic not defined",
"missing-config": "missing broker configuration",
"invalid-topic": "Invalid topic specified"
}
},
"httpin": {
"label": {
"method": "Method",
"url": "URL",
"doc": "Docs",
"return": "Return"
},
"setby": "- set by msg.method -",
"basicauth": "Use basic authentication?",
"utf8": "a UTF-8 string",
"binary": "a binary buffer",
"json": "a parsed JSON object",
"tip": {
"in": "The url will be relative to ",
"res": "The messages sent to this node <b>must</b> originate from an <i>http input</i> node",
"req": "Tip: If the JSON parse fails the fetched string is returned as-is."
},
"httpreq": "http request",
"errors": {
"not-created": "Cannot create http-in node when httpNodeRoot set to false",
"no-response": "No response object",
"json-error": "JSON parse error",
"no-url": "No url specified"
},
"status": {
"requesting": "requesting"
}
},
"websocket": {
"label": {
"type": "Type",
"path": "Path",
"url": "URL"
},
"listenon": "Listen on",
"connectto": "Connect to",
"payload": "Send/Receive payload",
"message": "Send/Receive entire message",
"tip": {
"path1": "By default, <code>payload</code> 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 ",
"url1": "URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.",
"url2": "By default, <code>payload</code> 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."
},
"errors": {
"connect-error": "An error occured on the ws connection: ",
"send-error": "An error occurred while sending: ",
"missing-conf": "Missing server configuration"
}
},
"watch": {
"label": {
"files": "File(s)"
},
"placeholder": {
"files": "Comma-separated list of files and/or directories"
},
"tip": "On Windows you must use double back-slashes \\\\ in any directory names."
},
"tcpin": {
"label": {
"type": "Type",
"output": "Output",
"port": "port",
"host": "at host",
"payload": "payload(s)",
"delimited": "delimited by",
"close-connection": "Close connection after each message is sent?",
"decode-base64": "Decode Base64 message?",
"server": "Server",
"return": "Return"
},
"type": {
"listen": "Listen on",
"connect": "Connect to",
"reply": "Reply to TCP"
},
"output": {
"stream": "stream of",
"single": "single",
"buffer": "Buffer",
"string": "String",
"base64": "Base64 String"
},
"return": {
"timeout": "after a fixed timeout of",
"character": "when character received is",
"number": "a fixed number of chars",
"never": "never - keep connection open"
},
"status": {
"connecting": "connecting to __host__:__port__",
"connected": "connected to __host__:__port__",
"listening-port": "listening on port __port__",
"stopped-listening": "stopped listening on port",
"connection-from": "connection from __host__:__port__",
"connection-closed": "connection closed from __host__:__port__",
"connections": "__count__ connection",
"connections_plural": "__count__ connections"
},
"errors": {
"connection-lost": "connection lost to __host__:__port__",
"timeout": "timeout closed socket port __port__",
"cannot-listen": "unable to listen on port __port__, error: __error__",
"error": "error: __error__",
"socket-error": "socket error from __host__:__port__",
"no-host": "Host and/or port not set",
"connect-timeout": "connect timeout",
"connect-fail": "connect failed"
}
},
"udp": {
"label": {
"listen": "Listen for",
"onport": "on Port",
"using": "using",
"output": "Output",
"group": "Group",
"interface": "Interface",
"send": "Send a",
"toport": "to port",
"address": "Address",
"decode-base64": "Decode Base64 encoded payload?"
},
"placeholder": {
"interface": "(optional) ip address of eth0",
"address": "destination ip"
},
"udpmsgs": "udp messages",
"mcmsgs": "multicast messages",
"udpmsg": "udp message",
"bcmsg": "broadcast message",
"mcmsg": "multicast message",
"output": {
"buffer": "a Buffer",
"string": "a String",
"base64": "a Base64 encoded string"
},
"bind": {
"random": "bind to random local port",
"local": "bind to local port",
"target": "bind to target port"
},
"tip": {
"in": "Tip: Make sure your firewall will allow the data in.",
"out": "Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>."
},
"status": {
"listener-at": "udp listener at __host__:__port__",
"mc-group": "udp multicast group __group__",
"listener-stopped": "udp listener stopped",
"output-stopped": "udp output stopped",
"mc-ready": "udp multicast ready: __outport__ -> __host__:__port__",
"bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__",
"ready": "udp ready: __outport__ -> __host__:__port__",
"ready-nolocal": "udp ready: __host__:__port__"
},
"errors": {
"access-error": "UDP access error, you may need root access for ports below 1024",
"error": "error: __error_",
"bad-mcaddress": "Bad Multicast Address",
"interface": "Must be ip address of the required interface",
"ip-notset": "udp: ip address not set",
"port-notset": "udp: port not set",
"port-invalid": "udp: port number not valid"
}
},
"switch": {
"label": {
"property": "Property",
"rule": "rule"
},
"checkall": "checking all rules",
"stopfirst": "stopping after first match",
"rules": {
"btwn":"is between",
"cont":"contains",
"regex":"matches regex",
"true":"is true",
"false":"is false",
"null":"is null",
"nnull":"is not null",
"else":"otherwise"
}
},
"change": {
"label": {
"rules": "Rules",
"rule": "rule",
"set": "set __property__",
"change": "change __property__",
"delete": "delete __property__",
"changeCount": "change: __count__ rules",
"regex": "Use regular expressions"
},
"action": {
"set": "Set",
"change": "Change",
"delete": "Delete",
"to": "to",
"search": "Search for",
"replace": "Replace with"
},
"errors": {
"invalid-from": "Invalid 'from' property: __error__"
}
},
"range": {
"label": {
"action": "Action",
"inputrange": "Map the input range",
"resultrange": "to the result range",
"from": "from",
"to": "to",
"roundresult": "Round result to the nearest integer?"
},
"placeholder": {
"min": "e.g. 0",
"maxin": "e.g. 99",
"maxout": "e.g. 255"
},
"scale": {
"payload": "Scale msg.payload",
"limit": "Scale and limit to the target range",
"wrap": "Scale and wrap within the target range"
},
"tip": "Tip: This node ONLY works with numbers.",
"errors": {
"notnumber": "Not a number"
}
},
"csv": {
"label": {
"columns": "Columns",
"separator": "Separator",
"c2o": "CSV-to-Object options",
"o2c": "Object-to-CSV options",
"input": "Input",
"firstrow": "first row contains column names",
"output": "Output",
"includerow": "include column name row",
"newline": "Newline"
},
"placeholder": {
"columns": "comma-separated column names"
},
"separator": {
"comma": "comma",
"tab": "tab",
"space": "space",
"semicolon": "semicolon",
"colon": "colon",
"hashtag": "hashtag",
"other": "other..."
},
"output": {
"row": "a message per row",
"array": "a single message [array]"
},
"newline": {
"linux": "Linux (\\n)",
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
"errors": {
"csv_js": "This node only handles csv strings or js objects."
}
},
"html": {
"label": {
"select": "Select",
"output": "Output"
},
"output": {
"html": "the html content of the elements",
"text": "only the text content of the elements"
},
"format": {
"single": "as a single message containing an array",
"multi": "as multiple messages, one for each element"
},
"tip": "Tip: The <b>Select</b> value is a <a href=\"https://github.com/fb55/CSSselect#user-content-supported-selectors\" target=\"_new\"><i><u>CSS Selector</u></i></a>, similar to a jQuery selector."
},
"json": {
"errors": {
"dropped-object": "Ignored non-object payload",
"dropped": "Ignored unsupported payload type"
}
},
"xml": {
"label": {
"represent": "Represent XML tag attributes as a property named",
"prefix": "Prefix to access character content",
"advanced": "Advanced options"
},
"tip": "There is no simple way to convert XML attributes to JSON so the approach taken here is to add a property, named $ by default, to the JSON structure.",
"errors": {
"xml_js": "This node only handles xml strings or js objects."
}
},
"rpi-gpio": {
"label": {
"gpiopin": "GPIO Pin",
"selectpin": "select pin",
"resistor": "Resistor?",
"readinitial": "Read initial state of pin on deploy/restart?",
"type": "Type",
"initpin": "Initialise pin state?",
"button": "Button",
"pimouse": "Pi Mouse",
"left": "Left",
"right": "Right",
"middle": "Middle"
},
"resistor": {
"none": "none",
"pullup": "pullup",
"pulldown": "pulldown"
},
"digout": "Digital output",
"pwmout": "PWM output",
"initpin0": "initial level of pin - low (0)",
"initpin1": "initial level of pin - high (1)",
"left": "left",
"right": "right",
"middle": "middle",
"any": "any",
"pinname": "Pin",
"alreadyuse": "already in use",
"alreadyset": "already set as",
"tip": {
"pin": "<b>Pins in Use</b>: ",
"in": "Tip: Only Digital Input is supported - input must be 0 or 1.",
"dig": "<b>Tip</b>: For digital output - input must be 0 or 1.",
"pwm": "<b>Tip</b>: For PWM output - input must be between 0 to 100."
},
"types": {
"digout": "digital output",
"input": "input",
"pullup": "input with pull up",
"pulldown": "input with pull down",
"pwmout": "PWM output"
},
"status": {
"stopped": "stopped",
"closed": "closed",
"not-running": "not running"
},
"errors": {
"ignorenode": "Ignoring Raspberry Pi specific node",
"version": "Version command failed",
"sawpitype": "Saw Pi Type",
"libnotfound": "Cannot find Pi RPi.GPIO python library",
"alreadyset": "GPIO pin __pin__ already set as type: __type__",
"invalidpin": "Invalid GPIO pin",
"invalidinput": "Invalid input",
"needtobeexecutable": "__command__ needs to be executable",
"mustbeexecutable": "nrgpio must to be executable",
"commandnotfound": "nrgpio command not found",
"commandnotexecutable": "nrgpio command not executable",
"error": "error: __error__",
"pythoncommandnotfound": "nrpgio python command not running"
}
},
"tail": {
"label": {
"filename": "Filename",
"splitlines": "Split lines on \\n?"
},
"errors": {
"windowsnotsupport": "Not currently supported on Windows."
}
},
"file": {
"label": {
"filename": "Filename",
"action": "Action",
"addnewline": "Add newline (\\n) to each payload?",
"createdir": "Create directory if it doesn't exist?",
"outputas": "Output as",
"filelabel": "file",
"deletelabel": "delete __file__"
},
"action": {
"append": "append to file",
"overwrite": "overwrite file",
"delete": "delete file"
},
"output": {
"utf8": "a utf8 string",
"buffer": "a Buffer"
},
"status": {
"wrotefile": "wrote to file: __file__",
"deletedfile": "deleted file: __file__",
"appendedfile": "appended to file: __file__"
},
"errors": {
"nofilename": "No filename specified",
"invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.",
"deletefail": "failed to delete file: __error__",
"writefail": "failed to write to file: __error__",
"appendfail": "failed to append to file: __error__",
"createfail": "failed to create file: __error__"
}
}
}

View File

@@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="switch">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row">
If msg.<input type="text" id="node-input-property" style="width: 200px;"/>
<span data-i18n="switch.label.property"></span> msg.<input type="text" id="node-input-property" style="width: 200px;"/>
</div>
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
@@ -28,12 +28,12 @@
</div>
</div>
<div class="form-row">
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a>
<a href="#" class="editor-button editor-button-small" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> <span data-i18n="switch.label.rule"></span></a>
</div>
<div class="form-row">
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
<option value="true">checking all rules</option>
<option value="false">stopping after first match</option>
<option value="true" data-i18n="switch.checkall"></option>
<option value="false" data-i18n="switch.stopfirst"></option>
</select>
</div>
</script>
@@ -72,14 +72,14 @@
{v:"lte",t:"<="},
{v:"gt",t:">"},
{v:"gte",t:">="},
{v:"btwn",t:"is between"},
{v:"cont",t:"contains"},
{v:"regex",t:"matches regex"},
{v:"true",t:"is true"},
{v:"false",t:"is false"},
{v:"null",t:"is null"},
{v:"nnull",t:"is not null"},
{v:"else",t:"otherwise"}
{v:"btwn",t:this._("switch.rules.btwn")},
{v:"cont",t:this._("switch.rules.cont")},
{v:"regex",t:this._("switch.rules.regex")},
{v:"true",t:this._("switch.rules.true")},
{v:"false",t:this._("switch.rules.false")},
{v:"null",t:this._("switch.rules.null")},
{v:"nnull",t:this._("switch.rules.nnull")},
{v:"else",t:this._("switch.rules.else")}
];
function generateRule(i,rule) {
@@ -99,10 +99,10 @@
btwnField.append(" and ");
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row);
var finalspan = $('<span/>',{style:"float: right;margin-right: 10px;"}).appendTo(row);
finalspan.append(' &#8594; <span class="node-input-rule-index">'+i+'</span> ');
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
selectField.change(function() {
@@ -171,7 +171,7 @@
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-rule-container-div").css("height",height+"px");
};
$( "#node-input-rule-container" ).sortable({
axis: "y",
update: function( event, ui ) {

View File

@@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="change">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> Rules</label>
<label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
</div>
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 300px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
@@ -28,7 +28,7 @@
</div>
</div>
<div class="form-row">
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a>
<a href="#" class="editor-button editor-button-small" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> <span data-i18n="change.label.rule"></span></a>
</div>
</script>
@@ -68,23 +68,23 @@
}
if (!this.rules) {
if (this.action === "replace") {
return "set msg."+this.property;
return this._("change.label.set",{property:"msg."+this.property});
} else if (this.action === "change") {
return "change msg."+this.property;
return this._("change.label.change",{property:"msg."+this.property});
} else {
return this.action+" msg."+this.property
return this._("change.label.delete",{property:"msg."+this.property});
}
} else {
if (this.rules.length == 1) {
if (this.rules[0].t === "set") {
return "set msg."+this.rules[0].p;
return this._("change.label.set",{property:"msg."+this.rules[0].p});
} else if (this.rules[0].t === "change") {
return "change msg."+this.rules[0].p;
return this._("change.label.change",{property:"msg."+this.rules[0].p});
} else {
return "delete msg."+this.rules[0].p;
return this._("change.label.delete",{property:"msg."+this.rules[0].p});
}
} else {
return "change: "+(this.rules.length||"no")+" rules";
return this._("change.label.changeCount",{count:this.rules.length});
}
}
},
@@ -92,52 +92,59 @@
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
var set = this._("change.action.set");
var change = this._("change.action.change");
var del = this._("change.action.delete");
var to = this._("change.action.to");
var search = this._("change.action.search");
var replace = this._("change.action.replace");
var regex = this._("change.label.regex");
if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
$("#node-input-action").change();
function generateRule(rule) {
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var row1 = $('<div/>').appendTo(container);
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 100px"}).appendTo(row1);
var selectOptions = [{v:"set",l:"Set"},{v:"change",l:"Change"},{v:"delete",l:"Delete"}];
var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del}];
for (var i=0;i<3;i++) {
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
}
$('<div/>',{style:"display:inline-block; width: 50px; text-align: right;"}).text("msg.").appendTo(row1);
var propertyName = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-name",type:"text"}).appendTo(row1);
var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row1);
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
var finalspan = $('<span/>',{style:"float: right; margin-right: 10px;"}).appendTo(row1);
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("to").appendTo(row2);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(to).appendTo(row2);
var propertyValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2);
var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("Search for").appendTo(row3_1);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(search).appendTo(row3_1);
var fromValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1);
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("replace with").appendTo(row3_2);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(replace).appendTo(row3_2);
var toValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2);
var row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000);
var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3);
$('<label/>',{for:id,style:"width: auto;"}).text("Use regular expressions").appendTo(row3_3);
$('<label/>',{for:id,style:"width: auto;"}).text(regex).appendTo(row3_3);
selectField.change(function() {
var type = $(this).val();
if (type == "set") {
row2.show();
row3.hide();
@@ -147,7 +154,6 @@
} else if (type == "delete") {
row2.hide();
row3.hide();
$("#node-tip").hide();
}
});
deleteButton.click(function() {
@@ -156,7 +162,7 @@
$(this).remove();
});
});
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
propertyName.val(rule.p);
propertyValue.val(rule.to);
@@ -164,19 +170,19 @@
toValue.val(rule.to);
useRegExp.prop('checked', rule.re);
selectField.change();
$("#node-input-rule-container").append(container);
}
$("#node-input-add-rule").click(function() {
generateRule({t:"replace",p:"payload"});
});
if (!this.rules) {
var rule = {
t:(this.action=="replace"?"set":this.action),
p:this.property
}
if (rule.t === "set") {
rule.to = this.to;
} else if (rule.t === "change") {
@@ -184,20 +190,20 @@
rule.to = this.to;
rule.re = this.reg;
}
delete this.to;
delete this.from;
delete this.reg;
delete this.action;
delete this.property;
this.rules = [rule];
}
for (var i=0;i<this.rules.length;i++) {
generateRule(this.rules[i]);
}
function changeDialogResize() {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = $("#dialog-form").height();

View File

@@ -52,7 +52,7 @@ module.exports = function(RED) {
rule.from = new RegExp(rule.from, "g");
} catch (e) {
valid = false;
this.error("Invalid 'from' property: "+e.message);
this.error(RED._("change.errors.invalid-from",{error:e.message}));
}
}
}

View File

@@ -16,35 +16,35 @@
<script type="text/x-red" data-template-name="range">
<div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> Action</label>
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
<select id="node-input-action" style="width:70%; margin-right:5px;">
<option value="scale">Scale msg.payload</option>
<option value="clamp">Scale and limit to the target range</option>
<option value="roll">Scale and wrap within the target range</option>
<option value="scale" data-i18n="range.scale.payload"></option>
<option value="clamp" data-i18n="range.scale.limit"></option>
<option value="roll" data-i18n="range.scale.wrap"></option>
</select>
</div>
<br/>
<div class="form-row"><i class="fa fa-sign-in"></i> Map the input range:</div>
<div class="form-row"><i class="fa fa-sign-in"></i> <span data-i18n="range.label.inputrange"></span>:</div>
<div class="form-row"><label></label>
from: <input type="text" id="node-input-minin" placeholder="e.g. 0" style="width:100px;"/>
&nbsp;&nbsp;to: <input type="text" id="node-input-maxin" placeholder="e.g. 99" style="width:100px;"/>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;"/>
</div>
<div class="form-row"><i class="fa fa-sign-out"></i> to the result range:</div>
<div class="form-row"><i class="fa fa-sign-out"></i> <span data-i18n="range.label.resultrange"></span>:</div>
<div class="form-row"><label></label>
from: <input type="text" id="node-input-minout" placeholder="e.g. 0" style="width:100px;"/>
&nbsp;&nbsp;to: <input type="text" id="node-input-maxout" placeholder="e.g. 255" style="width:100px;"/>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
</div>
<br/>
<div class="form-row"><label></label>
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-round">Round result to the nearest integer?</label></input>
<label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label></input>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="node-tip">Tip: This node ONLY works with numbers.</div>
<div class="form-tips" id="node-tip"><span data-i18n="range.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="range">

View File

@@ -42,7 +42,7 @@ module.exports = function(RED) {
if (node.round) { msg.payload = Math.round(msg.payload); }
node.send(msg);
}
else { node.log("Not a number: "+msg.payload); }
else { node.log(RED._("range.errors.notnumber")+": "+msg.payload); }
}
else { node.send(msg); } // If no payload - just pass it on.
});

View File

@@ -17,52 +17,52 @@
<script type="text/x-red" data-template-name="csv">
<div class="form-row">
<label for="node-input-temp"><i class="fa fa-list"></i> Columns</label>
<input type="text" id="node-input-temp" placeholder="comma-separated column names">
<label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label>
<input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns">
</div>
<div class="form-row">
<label for="node-input-select-sep"><i class="fa fa-text-width"></i> Separator</label>
<label for="node-input-select-sep"><i class="fa fa-text-width"></i> <span data-i18n="csv.label.separator"></span></label>
<select style="width: 150px" id="node-input-select-sep">
<option value=",">comma</option>
<option value="\t">tab</option>
<option value=" ">space</option>
<option value=";">semicolon</option>
<option value=":">colon</option>
<option value="#">hashtag</option>
<option value="">other...</option>
<option value="," data-i18n="csv.separator.comma"></option>
<option value="\t" data-i18n="csv.separator.tab"></option>
<option value=" " data-i18n="csv.separator.space"></option>
<option value=";" data-i18n="csv.separator.semicolon"></option>
<option value=":" data-i18n="csv.separator.colon"></option>
<option value="#" data-i18n="csv.separator.hashtag"></option>
<option value="" data-i18n="csv.separator.other"></option>
</select>
<input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<hr align="middle"/>
<div class="form-row">
<label style="width: 100%;"><i class="fa fa-gears"></i> CSV-to-Object options</label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Input</label>
<input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin">first row contains column names</span>
<label style="width: 100%;"><i class="fa fa-gears"></i> <span data-i18n="csv.label.c2o"></span></label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></span>
</div>
<div class="form-row">
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> Output</label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
<select type="text" id="node-input-multi" style="width: 250px;">
<option value="one">a message per row</option>
<option value="mult">a single message [array]</option>
<option value="one" data-i18n="csv.output.row"></option>
<option value="mult" data-i18n="csv.output.array"></option>
</select>
</div>
<hr align="middle"/>
<div class="form-row">
<label style="width: 100%;"><i class="fa fa-gears"></i> Object-to-CSV options</label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Output</label>
<input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout">include column name row</span>
<label style="width: 100%;"><i class="fa fa-gears"></i> <span data-i18n="csv.label.o2c"></span></label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.output"></span></label>
<input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span>
</div>
<div class="form-row">
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> Newline</label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> <span data-i18n="csv.label.newline"></span></label>
<select style="width: 150px" id="node-input-ret">
<option value='\n'>Linux (\n)</option>
<option value='\r'>Mac (\r)</option>
<option value='\r\n'>Windows (\r\n)</option>
<option value='\n' data-i18n="csv.newline.linux"></option>
<option value='\r' data-i18n="csv.newline.mac"></option>
<option value='\r\n' data-i18n="csv.newline.windows"></option>
</select>
</div>
</script>

View File

@@ -163,7 +163,7 @@ module.exports = function(RED) {
}
catch(e) { node.error(e,msg); }
}
else { node.warn("This node only handles csv strings or js objects."); }
else { node.warn(RED._("csv.errors.csv_js")); }
}
else { node.send(msg); } // If no payload - just pass it on.
});

View File

@@ -16,31 +16,31 @@
<script type="text/x-red" data-template-name="html">
<div class="form-row">
<label for="node-input-tag"><i class="fa fa-filter"></i> Select</label>
<input type="text" id="node-input-tag" placeholder="h1">
<label for="node-input-tag"><i class="fa fa-filter"></i> <span data-i18n="html.label.select"></span></label>
<input type="text" id="node-input-tag" style="width:73% !important" placeholder="h1">
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-sign-out"></i> Output</label>
<select id="node-input-ret" style="width:73% !important">
<option value="html">the html content of the elements</option>
<option value="text">only the text content of the elements</option>
<label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="html.label.output"></span></label>
<select id="node-input-ret" style="width:76% !important">
<option value="html" data-i18n="html.output.html"></option>
<option value="text"data-i18n="html.output.text"></option>
<!-- <option value="attr">an object of any attributes</option> -->
<!-- <option value="val">return the value from a form element</option> -->
</select>
</div>
<div class="form-row">
<label for="node-input-as">&nbsp;</label>
<select id="node-input-as" style="width:73% !important">
<option value="single">as a single message containing an array</option>
<option value="multi">as multiple messages, one for each element</option>
<select id="node-input-as" style="width:76% !important">
<option value="single" data-i18n="html.format.single"></option>
<option value="multi" data-i18n="html.format.multi"></option>
</select>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" style="width:73% !important" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips">Tip: The <b>Select</b> value is a <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new"><i><u>CSS Selector</u></i></a>, similar to a jQuery selector.</div>
<div class="form-tips"><span data-i18n="[html]html.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="html">

View File

@@ -16,8 +16,8 @@
<script type="text/x-red" data-template-name="json">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>

View File

@@ -35,9 +35,9 @@ module.exports = function(RED) {
msg.payload = JSON.stringify(msg.payload);
node.send(msg);
}
else { node.warn("Dropped: "+msg.payload); }
else { node.warn(RED._("json.errors.dropped-object")); }
}
else { node.warn("Dropped: "+msg.payload); }
else { node.warn(RED._("json.errors.dropped")); }
}
else { node.send(msg); } // If no payload - just pass it on.
});

View File

@@ -16,44 +16,30 @@
<script type="text/x-red" data-template-name="xml">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name" style="width:280px !important">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name" style="width:280px !important">
</div>
<div class="form-row" id="advanced">
</div>
<div id="advanced-options">
<div class="form-row">
<i class="fa fa-key"></i> Represent XML tag attributes as a property named <input type="text" id="node-input-attr" style="width:20px !important">
<i class="fa fa-key"></i> <span data-i18n="xml.label.represent"></span> <input type="text" id="node-input-attr" style="width:20px !important" placeholder="$">
</div>
<div class="form-row">
<i class="fa fa-key"></i> Prefix to access character content <input type="text" id="node-input-chr" style="width:20px !important">
<i class="fa fa-key"></i> <span data-i18n="xml.label.prefix"></span> <input type="text" id="node-input-chr" style="width:20px !important" placeholder="_">
</div>
<div class="form-tips">There is no simple way to convert XML attributes to JSON
so the approach taken here is to add a property, named $ by default, to the JSON structure.</div>
<div class="form-tips"><span data-i18n="xml.tip"></span></div>
</div>
<script> {
var showadvanced = showadvanced || true;
var showall = function() {
showadvanced = !showadvanced;
if (showadvanced) {
$("#advanced-options").show();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> Advanced options</label>');
}
else {
$("#advanced-options").hide();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> Advanced options ...</label>');
}
};
showall();
$("#advanced").click( function() { showall(); });
} </script>
</script>
<script type="text/x-red" data-help-name="xml">
<p>A function that parses the <b>msg.payload</b> to convert xml to/from a javascript object. Places the result in the payload.</p>
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
<p>If the input is a javascript object it tries to build an XML string.</p>
<p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
<p>You can also pass in a <b>msg.options</b> object to overide all the multitude of parameters. See
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a>
for more information.</p>
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>
</script>
<script type="text/javascript">
@@ -62,8 +48,8 @@
color:"#DEBD5C",
defaults: {
name: {value:""},
attr: {value:'$',required:true},
chr: {value:'_',required:true}
attr: {value:""},
chr: {value:""}
},
inputs:1,
outputs:1,
@@ -73,6 +59,23 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var showadvanced = showadvanced || true;
var advanced = this._("xml.label.advanced");
var showall = function() {
showadvanced = !showadvanced;
if (showadvanced) {
$("#advanced-options").show();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> '+advanced+'</label>');
}
else {
$("#advanced-options").hide();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> '+advanced+' ...</label>');
}
};
showall();
$("#advanced").click( function() { showall(); });
}
});
</script>

View File

@@ -22,17 +22,25 @@ module.exports = function(RED) {
function XMLNode(n) {
RED.nodes.createNode(this,n);
this.attrkey = n.attr || '$';
this.charkey = n.chr || '_';
this.attrkey = n.attr;
this.charkey = n.chr;
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") {
msg.payload = builder.buildObject(msg.payload);
if (typeof msg.payload === "object") {
var options = {};
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
options.async = false;
msg.payload = builder.buildObject(msg.payload, options);
node.send(msg);
}
else if (typeof msg.payload == "string") {
parseString(msg.payload, {strict:true,async:true,attrkey:node.attrkey,charkey:node.charkey}, function (err, result) {
var options = {};
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
options.async = true;
options.attrkey = node.attrkey || options.attrkey || '$';
options.charkey = node.charkey || options.charkey || '_';
parseString(msg.payload, options, function (err, result) {
if (err) { node.error(err, msg); }
else {
msg.payload = result;
@@ -40,7 +48,7 @@ module.exports = function(RED) {
}
});
}
else { node.warn("This node only handles xml strings or js objects."); }
else { node.warn(RED._("xml.errors.xml_js")); }
}
else { node.send(msg); } // If no payload - just pass it on.
});

View File

@@ -16,17 +16,17 @@
<script type="text/x-red" data-template-name="tail">
<div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="tail.label.filename"></span></label>
<input type="text" id="node-input-filename">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-split" style="width: 70%;">Split lines if we see \n ?</label>
<label for="node-input-split" style="width: 70%;"><span data-i18n="tail.label.splitlines"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<!-- <div class="form-tips">WON'T work on Windows.</div> -->
</script>

View File

@@ -20,7 +20,7 @@ module.exports = function(RED) {
var plat = require('os').platform();
if (plat.match(/^win/)) {
throw "Info : Currently not supported on Windows.";
throw RED._("tail.errors.windowsnotsupport");
}
function TailNode(n) {

View File

@@ -16,25 +16,30 @@
<script type="text/x-red" data-template-name="file">
<div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input type="text" id="node-input-filename">
</div>
<div class="form-row">
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> Action</label>
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
<select type="text" id="node-input-overwriteFile" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">append to file</option>
<option value="true">overwrite file</option>
<option value="delete">delete file</option>
<option value="false" data-i18n="file.action.append"></option>
<option value="true" data-i18n="file.action.overwrite"></option>
<option value="delete" data-i18n="file.action.delete"></option>
</select>
</div>
<div class="form-row" id="node-appline">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-appendNewline" style="width: 70%;">Add newline (\n) to each payload ?</label>
<input type="checkbox" id="node-input-appendNewline" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-appendNewline" style="width: 70%;"><span data-i18n="file.label.addnewline"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-createDir" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-createDir" style="width: 70%;"><span data-i18n="file.label.createdir"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -48,19 +53,19 @@
<script type="text/x-red" data-template-name="file in">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input type="text" id="node-input-filename" data-i18n="[placeholder]file.label.filename">
</div>
<div class="form-row">
<label for="node-input-format"><i class="fa fa-sign-out"></i> Output as</label>
<label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
<select id="node-input-format">
<option value="utf8">a utf8 string</option>
<option value="">a Buffer</option>
<option value="utf8" data-i18n="file.output.utf8"></option>
<option value="" data-i18n="file.output.buffer"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@@ -76,6 +81,7 @@
name: {value:""},
filename: {value:""},
appendNewline: {value:true},
createDir: {value:false},
overwriteFile: {value:"false"}
},
color:"BurlyWood",
@@ -84,8 +90,11 @@
icon: "file.png",
align: "right",
label: function() {
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; }
else { return this.name||this.filename||"file"; }
if (this.overwriteFile === "delete") {
return this.name||this._("file.label.deletelabel",{file:this.filename})
} else {
return this.name||this.filename||this._("file.label.filelabel");
}
},
labelStyle: function() {
return this.name?"node_label_italic":"";
@@ -110,7 +119,7 @@
outputs:1,
icon: "file.png",
label: function() {
return this.name||this.filename||"file";
return this.name||this.filename||this._("file.label.filelabel");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2014 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,55 +17,75 @@
module.exports = function(RED) {
"use strict";
var fs = require("fs-extra");
var os = require("os");
function FileNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename;
this.appendNewline = n.appendNewline;
this.overwriteFile = n.overwriteFile.toString();
this.createDir = n.createDir || false;
var node = this;
this.on("input",function(msg) {
var filename = this.filename || msg.filename || "";
if (msg.filename && n.filename && (n.filename !== msg.filename)) {
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
}
if (!this.filename) {
var filename = node.filename || msg.filename || "";
if (!node.filename) {
node.status({fill:"grey",shape:"dot",text:filename});
}
if (filename === "") {
node.warn('No filename specified');
} else if (msg.hasOwnProperty('delete')) { // remove warning at some point in future
node.warn("Warning: Invalid delete. Please use specific delete option in config dialog.");
//fs.unlink(filename, function (err) {
//if (err) { node.error('failed to delete file : '+err,msg); }
//});
} else if (msg.payload && (typeof msg.payload != "undefined")) {
node.warn(RED._("file.errors.nofilename"));
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var data = msg.payload;
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
if ((typeof data === "object") && (!Buffer.isBuffer(data))) {
data = JSON.stringify(data);
}
if (typeof data === "boolean") { data = data.toString(); }
if ((this.appendNewline)&&(!Buffer.isBuffer(data))) { data += "\n"; }
if (typeof data === "number") { data = data.toString(); }
if ((this.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
if (this.overwriteFile === "true") {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
fs.writeFile(filename, data, "binary", function (err) {
if (err) { node.error('failed to write to file : '+err,msg); }
else if (RED.settings.verbose) { node.log('wrote to file: '+filename); }
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
if (err) {
if ((err.code === "ENOENT") && node.createDir) {
fs.ensureFile(filename, function (err) {
if (err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); }
else {
fs.writeFile(filename, data, "binary", function (err) {
if (err) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
});
}
});
}
else { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
}
else if (RED.settings.verbose) { node.log(RED._("file.status.wrotefile",{file:filename})); }
});
}
else if (this.overwriteFile === "delete") {
fs.unlink(filename, function (err) {
if (err) { node.error('failed to delete file : '+err,msg); }
else if (RED.settings.verbose) { node.log("deleted file: "+filename); }
if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); }
else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); }
});
}
else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
fs.appendFile(filename, data, "binary", function (err) {
if (err) { node.error('failed to append to file : '+err,msg); }
else if (RED.settings.verbose) { node.log('appended to file: '+filename); }
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
if (err) {
if ((err.code === "ENOENT") && node.createDir) {
fs.ensureFile(filename, function (err) {
if (err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); }
else {
fs.appendFile(filename, data, "binary", function (err) {
if (err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
});
}
});
}
else { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
}
else if (RED.settings.verbose) { node.log(RED._("file.status.appendedfile",{file:filename})); }
});
}
}
@@ -85,15 +105,12 @@ module.exports = function(RED) {
options['encoding'] = this.format;
}
this.on("input",function(msg) {
var filename = this.filename || msg.filename || "";
if (msg.filename && n.filename && (n.filename !== msg.filename)) {
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
}
if (!this.filename) {
var filename = node.filename || msg.filename || "";
if (!node.filename) {
node.status({fill:"grey",shape:"dot",text:filename});
}
if (filename === "") {
node.warn('No filename specified');
node.warn(RED._("file.errors.nofilename"));
} else {
msg.filename = filename;
fs.readFile(filename,options,function(err,data) {

View File

@@ -1,6 +1,6 @@
{
"name" : "node-red",
"version" : "0.10.10",
"version" : "0.11.2",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",
@@ -25,23 +25,23 @@
"editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm", "flow"
],
"dependencies": {
"express": "3.17.2",
"express": "3.20.3",
"when": "3.7.3",
"bcryptjs": "2.1.0",
"nopt": "3.0.2",
"bcryptjs": "2.2.0",
"nopt": "3.0.3",
"mqtt": "0.3.x",
"ws": "0.7.2",
"fs-extra": "0.18.4",
"fs-extra": "0.22.1",
"clone": "1.0.2",
"mustache": "2.1.1",
"mustache": "2.1.2",
"cron":"1.0.9",
"raw-body":"2.1.1",
"xml2js":"0.4.9",
"raw-body":"2.1.2",
"xml2js":"0.4.12",
"sentiment":"0.2.3",
"follow-redirects":"0.0.3",
"follow-redirects":"0.0.6",
"cors":"2.7.1",
"cheerio":"0.19.0",
"uglify-js":"2.4.23",
"uglify-js":"2.4.24",
"on-headers":"1.0.0",
"is-utf8":"0.2.0",
"fs.notify":"0.0.4",
@@ -49,9 +49,12 @@
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"oauth2orize":"1.0.1",
"node-red-node-feedparser":"0.0.*",
"node-red-node-email":"0.0.*",
"node-red-node-twitter":"0.0.*"
"i18next":"1.10.2",
"semver": "5.0.1",
"node-red-node-feedparser":"0.1.*",
"node-red-node-email":"0.1.*",
"node-red-node-twitter":"0.1.*",
"node-red-node-rbe":"0.1.*"
},
"optionalDependencies": {
"node-red-node-serialport":"0.0.*",
@@ -61,7 +64,7 @@
"grunt": "0.4.5",
"grunt-chmod": "1.0.3",
"grunt-cli": "0.1.13",
"grunt-concurrent":"1.0.1",
"grunt-concurrent":"2.0.0",
"grunt-contrib-clean":"0.6.0",
"grunt-contrib-compress": "0.13.0",
"grunt-contrib-concat":"0.5.1",
@@ -69,15 +72,16 @@
"grunt-contrib-jshint": "0.11.2",
"grunt-contrib-uglify": "0.9.1",
"grunt-contrib-watch":"0.6.1",
"grunt-jsonlint":"1.0.4",
"grunt-nodemon":"0.4.0",
"grunt-sass":"1.0.0",
"grunt-simple-mocha": "0.4.0",
"mocha": "2.2.5",
"should": "6.0.3",
"sinon": "1.15.3",
"sinon": "1.15.4",
"supertest": "1.0.1"
},
"engines": {
"node": ">=0.8 <0.12"
"node": ">=0.8 <=0.12"
}
}

23
red.js
View File

@@ -23,6 +23,7 @@ var nopt = require("nopt");
var path = require("path");
var fs = require("fs");
var RED = require("./red/red.js");
var log = require("./red/log");
var server;
var app = express();
@@ -75,13 +76,13 @@ if (parsedArgs.settings) {
// NODE_RED_HOME contains user data - use its settings.js
settingsFile = path.join(process.env.NODE_RED_HOME,"settings.js");
} else {
var userSettingsFile = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,".node-red","settings.js");
var userSettingsFile = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,".node-red","settings.js");
if (fs.existsSync(userSettingsFile)) {
// $HOME/.node-red/settings.js exists
settingsFile = userSettingsFile;
} else {
// Use default settings.js
settingsFile = "./settings";
settingsFile = __dirname+"/settings.js";
}
}
}
@@ -166,7 +167,7 @@ try {
}
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
RED.log.warn("use of httpAdminAuth is deprecated. Use adminAuth instead");
RED.log.warn(log._("server.httpadminauth-deprecated"));
app.use(settings.httpAdminRoot,
express.basicAuth(function(user, pass) {
return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
@@ -216,10 +217,10 @@ RED.start().then(function() {
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
server.on('error', function(err) {
if (err.errno === "EADDRINUSE") {
RED.log.error('Unable to listen on '+getListenPath());
RED.log.error('Error: port in use');
RED.log.error(log._("server.unable-to-listen", {listenpath:getListenPath()}));
RED.log.error(log._("server.port-in-use"));
} else {
RED.log.error('Uncaught Exception:');
RED.log.error(log._("server.uncaught-exception"));
if (err.stack) {
RED.log.error(err.stack);
} else {
@@ -230,16 +231,16 @@ RED.start().then(function() {
});
server.listen(settings.uiPort,settings.uiHost,function() {
if (settings.httpAdminRoot === false) {
RED.log.info('Admin UI disabled');
RED.log.info(log._("server.admin-ui-disabled"));
}
process.title = 'node-red';
RED.log.info('Server now running at '+getListenPath());
RED.log.info(log._("server.now-running", {listenpath:getListenPath()}));
});
} else {
util.log('[red] Running in headless mode');
RED.log.info(log._("server.headless-mode"));
}
}).otherwise(function(err) {
RED.log.error("Failed to start server:");
RED.log.error(log._("server.failed-to-start"));
if (err.stack) {
RED.log.error(err.stack);
} else {
@@ -261,6 +262,6 @@ process.on('uncaughtException',function(err) {
process.on('SIGINT', function () {
RED.stop();
// TODO: need to allow nodes to close asynchronously before terminating the
// process - ie, promises
// process - ie, promises
process.exit();
});

View File

@@ -32,11 +32,15 @@ function hasPermission(userScope,permission) {
}
return true;
}
if (userScope == "*") {
if (permission === "") {
return true;
}
if (userScope === "*") {
return true;
}
if (util.isArray(permission)) {
for (i=0;i<permission.length;i++) {
if (!hasPermission(userScope,permission[i])) {
@@ -45,8 +49,8 @@ function hasPermission(userScope,permission) {
}
return true;
}
if (userScope == "read") {
if (userScope === "read") {
return readRE.test(permission);
} else {
return false; // anything not allowed is disallowed

View File

@@ -85,6 +85,9 @@ var passwordTokenExchange = function(client, username, password, scope, done) {
Users.authenticate(username,password).then(function(user) {
if (user) {
if (scope === "") {
scope = user.permissions;
}
if (permissions.hasPermission(user.permissions,scope)) {
loginAttempts = loginAttempts.filter(function(logEntry) {
return logEntry.user !== username;

View File

@@ -13,12 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require('express');
var fs = require("fs");
var events = require("../events");
var path = require("path");
var log = require("../log");
var redNodes = require("../nodes");
var settings = require("../settings");
@@ -34,7 +31,7 @@ module.exports = {
redNodes.setFlows(flows,deploymentType).then(function() {
res.send(204);
}).otherwise(function(err) {
log.warn("Error saving flows : "+err.message);
log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack);
res.json(500,{error:"unexpected_error", message:err.message});
});

Some files were not shown because too many files have changed in this diff Show More