1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'dev' into pr_1789

This commit is contained in:
Nick O'Leary 2018-10-22 10:46:47 +01:00
commit dc7e48dc53
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
713 changed files with 26906 additions and 8163 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/packages/node_modules/** linguist-generated=false

4
.gitignore vendored
View File

@ -17,3 +17,7 @@ node_modules
public public
locales/zz-ZZ locales/zz-ZZ
nodes/core/locales/zz-ZZ nodes/core/locales/zz-ZZ
!packages/node_modules
packages/node_modules/@node-red/editor-client/public
!test/**/node_modules
docs

View File

@ -1,5 +1,4 @@
/Gruntfile.js /Gruntfile.js
/.git/* /.git/*
/lib/*
*.backup *.backup
/public/* /public/*

View File

@ -1,7 +0,0 @@
.settings
.jshintignore
.jshintrc
.project
.tern-project
.travis.yml
.git

View File

@ -8,5 +8,3 @@ matrix:
before_script: before_script:
- npm install -g istanbul coveralls - npm install -g istanbul coveralls
- node_js: "8" - node_js: "8"
- node_js: "6"
- node_js: "4"

View File

@ -1,3 +1,115 @@
#### 0.19.4: Maintenance Release
- Fix race condition in non-cache lfs context Fixes #1888
- LocalFileSystem Context: Remove extra flush code
- Prevent race condition in caching mode of lfs context (#1889)
- Allow context store name to be provided in the key
- Switch node: only use promises when absolutely necessary
- Fix dbl-click handling on webkit-based browsers
- Ensure context.flow/global cannot be deleted or enumerated
- Handle context.get with multiple levels of unknown key Fixes #1883
- Fix global.get("foo.bar") for functionGlobalContext set values
- Fix node color bug (#1877)
- Merge pull request #1857 from cclauss/patch-1
- Define raw_input() in Python 3 & fix time.sleep()
#### 0.19.3: Maintenance Release
- Split node - fix complete to send msg for k/v object
- Remove unused Join node merged object key typed input
- Set the JavaScript editor to full-screen
- Filter global modules installed locally
- Add svg to permitted icon extension list
- Debug node - indicate status all the time if selected to do so
- pi nodes - increase test coverage slightly
- TCP-request node - only write payload
- JSON schema: perform validation when obj -> obj or str -> str
- JSON schema: add draft-06 support (via $schema keyword)
- Mqtt proxy configuration for websocket connection, #1651.
- Allows MQTT Shared Subscriptions for MQTT-In core node
- Fix use of HTML tag or CSS class specification as icon of typedInput
#### 0.19.2: Maintenance Release
- Ensure node default colour is used if palette.theme has no match
- fix lost messages / properties in TCPRequest Node; closes #1863 (#1864)
- Fix typo in template.html
- Improve error reporting from context plugin loading
- Prevent no-op edit of node marking as changed due to icon
- Change node must handle empty rule set
#### 0.19.1: Maintenance Release
- Pull in latest twitter node
- Handle windows paths for context storage
- Handle persisting objects with circular refs in context
- Ensure js editor can expand to fill available space
- Add example localfilesystem contextStorage to settings
- Fix template node handling of nested context tags
#### 0.19: Milestone Release
Editor
- Add editorTheme.palette.theme to allow overriding colours
- Index all node properties when searching Fixes #1446
- Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779
- Prevent horizontal scroll when palette name cannot wrap
- Ignore middle-click on node/ports to enable panning
- Better wire layout when looping back
- fix appearence of retry button of remote branch management dialog
- Handle releasing ctrl when using quick-add node dialog
- Add $env function to JSONata expressions
- Widen support for env var to use ${} or $() syntax
- Add env-var support to TypedInput
- Show unknown node properties in info tab
- Add node icon picker widget
- Only edit nodes on dbl click on primary button with no modifiers
- Allow subflows to be put in any palette category
- Add flow navigator widget
- Cache flow library result to improve response time Fixes #1753
- Add middle-button-drag to pan the workspace
- allow multi-line category name in editor
- Redesign sidebar tabs
- Do not disable the export-clipboard menu option with empty selection
Nodes
- Change: Ensure runtime errors in Change node can be caught Fixes #1769
- File: Add output to File Out node
- Function: add expandable JavaScript editor pane
- Function: allow id and name reference in function node code (#1731)
- HTTP Request: Move to request module
- HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278
- Join: accumulate top level properties
- Join: allow environment variable as reduce init value
- JSON: add JSON schema validation via msg.schema
- Pi: Let nrgpio code work with python 3
- Pi: let Pi nodes be visible/editable on all platforms
- Switch: add isEmpty rule
- TCP: queue messages while connecting; closes #1414
- TLS: Add servername option to TLS config node for SNI Fixes #1805
- UDP: Don't accidentally re-use udp port when set to not do so
Persistent Context
- Add Context data sidebar
- Add persistable context option
- Add default memory store
- Add file-based context store
- Add async mode to evaluateJSONataExpression
- Update RED.util.evaluateNodeProperty to support context stores
Runtime
- Support flow.disabled and .info in /flow API
- Node errors should be Strings not Errors Fixes #1781
- Add detection of connection timeout in git communication Fixes #1770
- Handle loading empty nodesDir
- Add 'private' property to userDir generated package.json
- Add RED.require to allow nodes to access other modules
- Ensure add/remove modules are run sequentially
#### 0.18.7: Maintenance Release #### 0.18.7: Maintenance Release
Editor Fixes Editor Fixes

View File

@ -42,7 +42,7 @@ module.exports = function(grunt) {
reporter: 'spec' reporter: 'spec'
}, },
all: { src: ['test/**/*_spec.js'] }, all: { src: ['test/**/*_spec.js'] },
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]}, core: { src: ["test/_spec.js","test/unit/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]} nodes: { src: ["test/nodes/**/*_spec.js"]}
}, },
webdriver: { webdriver: {
@ -59,8 +59,8 @@ module.exports = function(grunt) {
reportFormats: ['lcov','html'], reportFormats: ['lcov','html'],
print: 'both' print: 'both'
}, },
all: { src: ["test/_spec.js","test/red/**/*_spec.js","test/nodes/**/*_spec.js"] }, all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]}, core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]} nodes: { src: ["test/nodes/**/*_spec.js"]}
}, },
jshint: { jshint: {
@ -80,16 +80,14 @@ module.exports = function(grunt) {
all: [ all: [
'Gruntfile.js', 'Gruntfile.js',
'red.js', 'red.js',
'red/**/*.js', 'packages/**/*.js'
'nodes/core/*/*.js',
'editor/js/**/*.js'
], ],
core: { core: {
files: { files: {
src: [ src: [
'Gruntfile.js', 'Gruntfile.js',
'red.js', 'red.js',
'red/**/*.js' 'packages/**/*.js',
] ]
} }
}, },
@ -120,80 +118,83 @@ module.exports = function(grunt) {
src: [ src: [
// Ensure editor source files are concatenated in // Ensure editor source files are concatenated in
// the right order // the right order
"editor/js/red.js", "packages/node_modules/@node-red/editor-client/src/js/red.js",
"editor/js/events.js", "packages/node_modules/@node-red/editor-client/src/js/events.js",
"editor/js/i18n.js", "packages/node_modules/@node-red/editor-client/src/js/i18n.js",
"editor/js/settings.js", "packages/node_modules/@node-red/editor-client/src/js/settings.js",
"editor/js/user.js", "packages/node_modules/@node-red/editor-client/src/js/user.js",
"editor/js/comms.js", "packages/node_modules/@node-red/editor-client/src/js/comms.js",
"editor/js/text/bidi.js", "packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
"editor/js/text/format.js", "packages/node_modules/@node-red/editor-client/src/js/text/format.js",
"editor/js/ui/state.js", "packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
"editor/js/nodes.js", "packages/node_modules/@node-red/editor-client/src/js/nodes.js",
"editor/js/history.js", "packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
"editor/js/validators.js", "packages/node_modules/@node-red/editor-client/src/js/history.js",
"editor/js/ui/utils.js", "packages/node_modules/@node-red/editor-client/src/js/validators.js",
"editor/js/ui/common/editableList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
"editor/js/ui/common/checkboxSet.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
"editor/js/ui/common/menu.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/checkboxSet.js",
"editor/js/ui/common/panels.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js",
"editor/js/ui/common/popover.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/panels.js",
"editor/js/ui/common/searchBox.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js",
"editor/js/ui/common/tabs.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js",
"editor/js/ui/common/stack.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js",
"editor/js/ui/common/typedInput.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
"editor/js/ui/actions.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
"editor/js/ui/deploy.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
"editor/js/ui/diff.js", "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
"editor/js/ui/keyboard.js", "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
"editor/js/ui/workspaces.js", "packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
"editor/js/ui/view.js", "packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
"editor/js/ui/view-navigator.js", "packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
"editor/js/ui/sidebar.js", "packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js",
"editor/js/ui/palette.js", "packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
"editor/js/ui/tab-info.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette.js",
"editor/js/ui/tab-config.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js",
"editor/js/ui/tab-context.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js",
"editor/js/ui/palette-editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
"editor/js/ui/editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
"editor/js/ui/tray.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
"editor/js/ui/clipboard.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
"editor/js/ui/library.js", "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
"editor/js/ui/notifications.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
"editor/js/ui/search.js", "packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
"editor/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/library.js",
"editor/js/ui/subflow.js", "packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js",
"editor/js/ui/userSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/search.js",
"editor/js/ui/projects/projects.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
"editor/js/ui/projects/projectSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
"editor/js/ui/projects/projectUserSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
"editor/js/ui/projects/tab-versionControl.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
"editor/js/ui/touch/radialMenu.js" "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js"
], ],
dest: "public/red/red.js" dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
}, },
vendor: { vendor: {
files: { files: {
"public/vendor/vendor.js": [ "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [
"editor/vendor/jquery/js/jquery-1.11.3.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-1.11.3.min.js",
"editor/vendor/bootstrap/js/bootstrap.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/bootstrap/js/bootstrap.min.js",
"editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"editor/vendor/marked/marked.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js",
"editor/vendor/d3/d3.v3.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
"editor/vendor/i18next/i18next.min.js" "packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js"
], ],
"public/vendor/vendor.css": [ "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
// TODO: resolve relative resource paths in // TODO: resolve relative resource paths in
// bootstrap/FA/jquery // bootstrap/FA/jquery
], ],
"public/vendor/jsonata/jsonata.min.js": [ "packages/node_modules/@node-red/editor-client/public/vendor/jsonata/jsonata.min.js": [
"node_modules/jsonata/jsonata-es5.min.js", "node_modules/jsonata/jsonata-es5.min.js",
"editor/vendor/jsonata/formatter.js" "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js"
], ],
"public/vendor/ace/worker-jsonata.js": [ "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
"node_modules/jsonata/jsonata-es5.min.js", "node_modules/jsonata/jsonata-es5.min.js",
"editor/vendor/jsonata/worker-jsonata.js" "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
] ]
} }
} }
@ -201,10 +202,10 @@ module.exports = function(grunt) {
uglify: { uglify: {
build: { build: {
files: { files: {
'public/red/red.min.js': 'public/red/red.js', 'packages/node_modules/@node-red/editor-client/public/red/red.min.js': 'packages/node_modules/@node-red/editor-client/public/red/red.js',
'public/red/main.min.js': 'public/red/main.js', 'packages/node_modules/@node-red/editor-client/public/red/main.min.js': 'packages/node_modules/@node-red/editor-client/public/red/main.js',
'public/vendor/ace/mode-jsonata.js': 'editor/vendor/jsonata/mode-jsonata.js', 'packages/node_modules/@node-red/editor-client/public/vendor/ace/mode-jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js',
'public/vendor/ace/snippets/jsonata.js': 'editor/vendor/jsonata/snippets-jsonata.js' 'packages/node_modules/@node-red/editor-client/public/vendor/ace/snippets/jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/snippets-jsonata.js'
} }
} }
}, },
@ -214,50 +215,50 @@ module.exports = function(grunt) {
outputStyle: 'compressed' outputStyle: 'compressed'
}, },
files: [{ files: [{
dest: 'public/red/style.min.css', dest: 'packages/node_modules/@node-red/editor-client/public/red/style.min.css',
src: 'editor/sass/style.scss' src: 'packages/node_modules/@node-red/editor-client/src/sass/style.scss'
}, },
{ {
dest: 'public/vendor/bootstrap/css/bootstrap.min.css', dest: 'packages/node_modules/@node-red/editor-client/public/vendor/bootstrap/css/bootstrap.min.css',
src: 'editor/vendor/bootstrap/css/bootstrap.css' src: 'packages/node_modules/@node-red/editor-client/src/vendor/bootstrap/css/bootstrap.css'
}] }]
} }
}, },
jsonlint: { jsonlint: {
messages: { messages: {
src: [ src: [
'nodes/core/locales/en-US/messages.json', 'packages/node_modules/@node-red/nodes/locales/**/*.json',
'red/api/locales/en-US/editor.json', 'packages/node_modules/@node-red/editor-client/locales/**/*.json',
'red/runtime/locales/en-US/runtime.json' 'packages/node_modules/@node-red/runtime/locales/**/*.json'
] ]
}, },
keymaps: { keymaps: {
src: [ src: [
'editor/js/keymap.json' 'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
] ]
} }
}, },
attachCopyright: { attachCopyright: {
js: { js: {
src: [ src: [
'public/red/red.min.js', 'packages/node_modules/@node-red/editor-client/public/red/red.min.js',
'public/red/main.min.js' 'packages/node_modules/@node-red/editor-client/public/red/main.min.js'
] ]
}, },
css: { css: {
src: [ src: [
'public/red/style.min.css' 'packages/node_modules/@node-red/editor-client/public/red/style.min.css'
] ]
} }
}, },
clean: { clean: {
build: { build: {
src: [ src: [
"public/red", "packages/node_modules/@node-red/editor-client/public/red",
"public/index.html", "packages/node_modules/@node-red/editor-client/public/index.html",
"public/favicon.ico", "packages/node_modules/@node-red/editor-client/public/favicon.ico",
"public/icons", "packages/node_modules/@node-red/editor-client/public/icons",
"public/vendor" "packages/node_modules/@node-red/editor-client/public/vendor"
] ]
}, },
release: { release: {
@ -269,27 +270,27 @@ module.exports = function(grunt) {
watch: { watch: {
js: { js: {
files: [ files: [
'editor/js/**/*.js' 'packages/node_modules/@node-red/editor-client/src/js/**/*.js'
], ],
tasks: ['copy:build','concat','uglify','attachCopyright:js'] tasks: ['copy:build','concat','uglify','attachCopyright:js']
}, },
sass: { sass: {
files: [ files: [
'editor/sass/**/*.scss' 'packages/node_modules/@node-red/editor-client/src/sass/**/*.scss'
], ],
tasks: ['sass','attachCopyright:css'] tasks: ['sass','attachCopyright:css']
}, },
json: { json: {
files: [ files: [
'nodes/core/locales/en-US/messages.json', 'packages/node_modules/@node-red/nodes/locales/**/*.json',
'red/api/locales/en-US/editor.json', 'packages/node_modules/@node-red/editor-client/locales/**/*.json',
'red/runtime/locales/en-US/runtime.json' 'packages/node_modules/@node-red/runtime/locales/**/*.json'
], ],
tasks: ['jsonlint:messages'] tasks: ['jsonlint:messages']
}, },
keymaps: { keymaps: {
files: [ files: [
'editor/js/keymap.json' 'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
], ],
tasks: ['jsonlint:keymaps','copy:build'] tasks: ['jsonlint:keymaps','copy:build']
}, },
@ -304,12 +305,13 @@ module.exports = function(grunt) {
nodemon: { nodemon: {
/* uses .nodemonignore */ /* uses .nodemonignore */
dev: { dev: {
script: 'red.js', script: 'packages/node_modules/node-red/red.js',
options: { options: {
args: nodemonArgs, args: nodemonArgs,
ext: 'js,html,json', ext: 'js,html,json',
watch: [ watch: [
'red','nodes' 'packages/node_modules',
'!packages/node_modules/@node-red/editor-client'
] ]
} }
} }
@ -328,21 +330,21 @@ module.exports = function(grunt) {
build: { build: {
files:[ files:[
{ {
src: 'editor/js/main.js', src: 'packages/node_modules/@node-red/editor-client/src/js/main.js',
dest: 'public/red/main.js' dest: 'packages/node_modules/@node-red/editor-client/public/red/main.js'
}, },
{ {
src: 'editor/js/keymap.json', src: 'packages/node_modules/@node-red/editor-client/src/js/keymap.json',
dest: 'public/red/keymap.json' dest: 'packages/node_modules/@node-red/editor-client/public/red/keymap.json'
}, },
{ {
cwd: 'editor/images', cwd: 'packages/node_modules/@node-red/editor-client/src/images',
src: '**', src: '**',
expand: true, expand: true,
dest: 'public/red/images/' dest: 'packages/node_modules/@node-red/editor-client/public/red/images/'
}, },
{ {
cwd: 'editor/vendor', cwd: 'packages/node_modules/@node-red/editor-client/src/vendor',
src: [ src: [
'ace/**', 'ace/**',
//'bootstrap/css/**', //'bootstrap/css/**',
@ -351,46 +353,31 @@ module.exports = function(grunt) {
'font-awesome/**' 'font-awesome/**'
], ],
expand: true, expand: true,
dest: 'public/vendor/' dest: 'packages/node_modules/@node-red/editor-client/public/vendor/'
}, },
{ {
cwd: 'editor/icons', cwd: 'packages/node_modules/@node-red/editor-client/src/icons',
src: '**', src: '**',
expand: true, expand: true,
dest: 'public/icons/' dest: 'packages/node_modules/@node-red/editor-client/public/icons/'
}, },
{ {
expand: true, expand: true,
src: ['editor/index.html','editor/favicon.ico'], src: ['packages/node_modules/@node-red/editor-client/src/index.html','packages/node_modules/@node-red/editor-client/src/favicon.ico'],
dest: 'public/', dest: 'packages/node_modules/@node-red/editor-client/public/',
flatten: true flatten: true
}, },
{ {
src: 'CHANGELOG.md', src: 'CHANGELOG.md',
dest: 'public/red/about' dest: 'packages/node_modules/@node-red/editor-client/public/red/about'
},
{
cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/',
src: '**',
expand: true,
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/'
} }
] ]
},
release: {
files: [{
mode: true,
expand: true,
src: [
'*.md',
'LICENSE',
'package.json',
'settings.js',
'red.js',
'lib/.gitignore',
'nodes/*.demo',
'nodes/core/**',
'red/**',
'public/**',
'editor/templates/**',
'bin/**'
],
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
}]
} }
}, },
chmod: { chmod: {
@ -399,8 +386,8 @@ module.exports = function(grunt) {
}, },
release: { release: {
src: [ src: [
path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>/nodes/core/hardware/nrgpio*'), "packages/node_modules/@node-red/nodes/core/hardware/nrgpio",
path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>/red/runtime/storage/localfilesystem/projects/git/node-red-*sh') "packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-*sh"
] ]
} }
}, },
@ -410,8 +397,43 @@ module.exports = function(grunt) {
archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip' archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip'
}, },
expand: true, expand: true,
cwd: '<%= paths.dist %>/', cwd: 'packages/node_modules/',
src: ['node-red-<%= pkg.version %>/**'] src: [
'**',
'!@node-red/editor-client/src/**'
]
}
},
jsdoc : {
runtimeAPI: {
src: 'packages/node_modules/@node-red/runtime/lib/api/*.js',
options: {
destination: 'docs',
configure: './jsdoc.json'
}
},
nodeREDUtil: {
src: 'packages/node_modules/@node-red/util/**/*.js',
options: {
destination: 'packages/node_modules/@node-red/util/docs',
configure: './jsdoc.json'
}
}
},
jsdoc2md: {
runtimeAPI: {
options: {
separators: true
},
src: 'packages/node_modules/@node-red/runtime/lib/api/*.js',
dest: 'docs/runtime-api.md'
},
nodeREDUtil: {
options: {
separators: true
},
src: 'packages/node_modules/@node-red/util/**/*.js',
dest: 'packages/node_modules/@node-red/util/docs/api.md'
} }
} }
}); });
@ -431,6 +453,8 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-jsonlint'); grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-mocha-istanbul'); grunt.loadNpmTasks('grunt-mocha-istanbul');
grunt.loadNpmTasks('grunt-webdriver'); grunt.loadNpmTasks('grunt-webdriver');
grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
grunt.registerMultiTask('attachCopyright', function() { grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src; var files = this.data.src;
@ -472,6 +496,15 @@ module.exports = function(grunt) {
} }
}); });
grunt.registerTask('verifyPackageDependencies', function() {
var verifyDependencies = require("./scripts/verify-package-dependencies.js");
var failures = verifyDependencies();
if (failures.length > 0) {
failures.forEach(f => grunt.log.error(f));
grunt.fail.fatal("Failed to verify package dependencies");
}
});
grunt.registerTask('setDevEnv', grunt.registerTask('setDevEnv',
'Sets NODE_ENV=development so non-minified assets are used', 'Sets NODE_ENV=development so non-minified assets are used',
function () { function () {
@ -480,7 +513,7 @@ module.exports = function(grunt) {
grunt.registerTask('default', grunt.registerTask('default',
'Builds editor content then runs code style checks and unit tests on all components', 'Builds editor content then runs code style checks and unit tests on all components',
['build','jshint:editor','mocha_istanbul:all']); ['build','verifyPackageDependencies','jshint:editor','mocha_istanbul:all']);
grunt.registerTask('test-core', grunt.registerTask('test-core',
'Runs code style check and unit tests on core runtime code', 'Runs code style check and unit tests on core runtime code',
@ -508,9 +541,13 @@ module.exports = function(grunt) {
grunt.registerTask('release', grunt.registerTask('release',
'Create distribution zip file', 'Create distribution zip file',
['build','clean:release','copy:release','chmod:release','compress:release']); ['build','verifyPackageDependencies','clean:release','chmod:release','compress:release']);
grunt.registerTask('coverage', grunt.registerTask('coverage',
'Run Istanbul code test coverage task', 'Run Istanbul code test coverage task',
['build','mocha_istanbul:all']); ['build','mocha_istanbul:all']);
grunt.registerTask('docs',
'Generates API documentation',
['jsdoc','jsdoc2md']);
}; };

View File

@ -2,7 +2,7 @@
http://nodered.org http://nodered.org
[![Build Status](https://travis-ci.org/node-red/node-red.svg)](https://travis-ci.org/node-red/node-red) [![Build Status](https://travis-ci.org/node-red/node-red.svg?branch=master)](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) [![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. A visual tool for wiring the Internet of Things.
@ -44,9 +44,6 @@ If you want to run the latest code from git, here's how to get started:
4. Run 4. Run
npm start npm start
or
node red.js
## Contributing ## Contributing

View File

@ -1,81 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.panels = (function() {
function createPanel(options) {
var container = options.container || $("#"+options.id);
var children = container.children();
if (children.length !== 2) {
throw new Error("Container must have exactly two children");
}
container.addClass("red-ui-panels");
var separator = $('<div class="red-ui-panels-separator"></div>').insertAfter(children[0]);
var startPosition;
var panelHeights = [];
var modifiedHeights = false;
var panelRatio;
separator.draggable({
axis: "y",
containment: container,
scroll: false,
start:function(event,ui) {
var height = container.height();
startPosition = ui.position.top;
panelHeights = [$(children[0]).height(),$(children[1]).height()];
},
drag: function(event,ui) {
var height = container.height();
var delta = ui.position.top-startPosition;
var newHeights = [panelHeights[0]+delta,panelHeights[1]-delta];
$(children[0]).height(newHeights[0]);
$(children[1]).height(newHeights[1]);
if (options.resize) {
options.resize(newHeights[0],newHeights[1]);
}
ui.position.top -= delta;
panelRatio = newHeights[0]/height;
},
stop:function(event,ui) {
modifiedHeights = true;
}
});
return {
resize: function(height) {
var panelHeights = [$(children[0]).height(),$(children[1]).height()];
container.height(height);
if (modifiedHeights) {
var topPanelHeight = panelRatio*height;
var bottomPanelHeight = height - topPanelHeight - 48;
panelHeights = [topPanelHeight,bottomPanelHeight];
$(children[0]).height(panelHeights[0]);
$(children[1]).height(panelHeights[1]);
}
if (options.resize) {
options.resize(panelHeights[0],panelHeights[1]);
}
}
}
}
return {
create: createPanel
}
})();

View File

@ -1,8 +0,0 @@
.ace_gutter {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ace_scroller {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}

View File

@ -1,229 +0,0 @@
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<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 JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<head>
<title>{{ page.title }}</title>
<link rel="icon" type="image/png" href="{{ page.favicon }}">
<link rel="mask-icon" href="{{ page.tabicon }}" color="#8f0000">
<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">
{{#page.css}}
<link rel="stylesheet" href="{{.}}">
{{/page.css}}
</head>
<body spellcheck="false">
<div id="header">
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}" title="{{version}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</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 id="header-shade" class="hide"></div>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="workspace">
<ul id="workspace-tabs"></ul>
<div id="chart" tabindex="1"></div>
<div id="workspace-toolbar"></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>
<a class="workspace-footer-button-toggle single" id="btn-navigate" href="#"><i class="fa fa-map-o"></i></a>
</div>
<div id="editor-shade" class="hide"></div>
</div>
<div id="editor-stack"></div>
<div id="palette">
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-search" class="palette-search hide">
<input type="text" data-i18n="[placeholder]palette.filter"></input>
</div>
<div id="palette-container" class="palette-scroll hide"></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 id="palette-shade" class="hide"></div>
</div><!-- /palette -->
<div id="sidebar">
<ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div>
<div id="sidebar-footer"></div>
<div id="sidebar-shade" class="hide"></div>
</div>
<div id="sidebar-separator"></div>
</div>
<div id="full-shade" class="hide"></div>
<div id="notifications"></div>
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
</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" 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" 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>
<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="#" data-i18n="[append]library.breadcrumb"></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>
<script type="text/x-red" data-template-name="subflow">
<div class="form-row">
<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>
<script type="text/x-red" data-template-name="subflow-template">
<div class="form-row">
<i class="fa fa-tag"></i>
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div>
<div class="form-row">
<i class="fa fa-folder-o"></i>
<label for="subflow-input-category" data-i18n="editor:subflow.category"></label><select style="width: 250px;" id="subflow-input-category"></select><input style="display:none; margin-left: 10px; width:calc(100% - 250px)" type="text" id="subflow-input-custom-category">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="subflow-input-info" data-i18n="editor:subflow.info"></label>
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div>
</div>
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</script>
<script type="text/x-red" data-template-name="_expression">
<div id="node-input-expression-panels">
<div id="node-input-expression-panel-expr" class="red-ui-panel">
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
<span class="node-input-expression-legacy"><i class="fa fa-exclamation-circle"></i> <span data-i18n="expressionEditor.compatMode"></span></span>
<button id="node-input-expression-reformat" class="editor-button editor-button-small"><span data-i18n="expressionEditor.format"></span></button>
</div>
<div class="form-row node-text-editor-row">
<div class="node-text-editor" id="node-input-expression"></div>
</div>
</div>
<div id="node-input-expression-panel-info" class="red-ui-panel">
<div class="form-row">
<ul id="node-input-expression-tabs"></ul>
<div id="node-input-expression-tab-help" class="node-input-expression-tab-content hide">
<div>
<select id="node-input-expression-func"></select>
<button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button>
</div>
<div id="node-input-expression-help"></div>
</div>
<div id="node-input-expression-tab-test" class="node-input-expression-tab-content hide">
<div>
<span style="display: inline-block; width: calc(50% - 5px);">
<span data-i18n="expressionEditor.data"></span>
<button style="float: right; margin-right: 5px;" id="node-input-example-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button>
</span>
<span style="display: inline-block; width: calc(50% - 5px);" data-i18n="expressionEditor.result"></span>
</div>
<div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-data"></div>
<div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-result"></div>
</div>
</div>
</div>
</div>
</script>
<script type="text/x-red" data-template-name="_json">
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
<button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>
</div>
</script>
<script type="text/x-red" data-template-name="_markdown">
<div class="form-row" id="node-input-markdown-title" style="margin-bottom: 3px; text-align: right;">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-markdown"></div>
</div>
</script>
<script type="text/x-red" data-template-name="_buffer">
<div id="node-input-buffer-panels">
<div id="node-input-buffer-panel-str" class="red-ui-panel">
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
<span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span>
</div>
<div class="form-row node-text-editor-row">
<div class="node-text-editor" id="node-input-buffer-str"></div>
</div>
</div>
<div id="node-input-buffer-panel-bin" class="red-ui-panel">
<div class="form-row node-text-editor-row" style="margin-top: 10px">
<div class="node-text-editor" id="node-input-buffer-bin"></div>
</div>
</div>
</div>
</script>
<script src="vendor/vendor.js"></script>
<script src="vendor/jsonata/jsonata.min.js"></script>
<script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script>
<script src="{{ asset.red }}"></script>
<script src="{{ asset.main }}"></script>
{{# page.scripts }}
<script src="{{.}}"></script>
{{/ page.scripts }}
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
ace.define("ace/mode/python_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="and|as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield|async|await",t="True|False|None|NotImplemented|Ellipsis|__debug__",n="abs|divmod|input|open|staticmethod|all|enumerate|int|ord|str|any|eval|isinstance|pow|sum|basestring|execfile|issubclass|print|super|binfile|iter|property|tuple|bool|filter|len|range|type|bytearray|float|list|raw_input|unichr|callable|format|locals|reduce|unicode|chr|frozenset|long|reload|vars|classmethod|getattr|map|repr|xrange|cmp|globals|max|reversed|zip|compile|hasattr|memoryview|round|__import__|complex|hash|min|set|apply|delattr|help|next|setattr|buffer|dict|hex|object|slice|coerce|dir|id|oct|sorted|intern",r=this.createKeywordMapper({"invalid.deprecated":"debugger","support.function":n,"constant.language":t,keyword:e},"identifier"),i="(?:r|u|ur|R|U|UR|Ur|uR)?",s="(?:(?:[1-9]\\d*)|(?:0))",o="(?:0[oO]?[0-7]+)",u="(?:0[xX][\\dA-Fa-f]+)",a="(?:0[bB][01]+)",f="(?:"+s+"|"+o+"|"+u+"|"+a+")",l="(?:[eE][+-]?\\d+)",c="(?:\\.\\d+)",h="(?:\\d+)",p="(?:(?:"+h+"?"+c+")|(?:"+h+"\\.))",d="(?:(?:"+p+"|"+h+")"+l+")",v="(?:"+d+"|"+p+")",m="\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv'\"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})";this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"string",regex:i+'"{3}',next:"qqstring3"},{token:"string",regex:i+'"(?=.)',next:"qqstring"},{token:"string",regex:i+"'{3}",next:"qstring3"},{token:"string",regex:i+"'(?=.)",next:"qstring"},{token:"constant.numeric",regex:"(?:"+v+"|\\d+)[jJ]\\b"},{token:"constant.numeric",regex:v},{token:"constant.numeric",regex:f+"[lL]\\b"},{token:"constant.numeric",regex:f+"\\b"},{token:r,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|%|<<|>>|&|\\||\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\[\\(\\{]"},{token:"paren.rparen",regex:"[\\]\\)\\}]"},{token:"text",regex:"\\s+"}],qqstring3:[{token:"constant.language.escape",regex:m},{token:"string",regex:'"{3}',next:"start"},{defaultToken:"string"}],qstring3:[{token:"constant.language.escape",regex:m},{token:"string",regex:"'{3}",next:"start"},{defaultToken:"string"}],qqstring:[{token:"constant.language.escape",regex:m},{token:"string",regex:"\\\\$",next:"qqstring"},{token:"string",regex:'"|$',next:"start"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:m},{token:"string",regex:"\\\\$",next:"qstring"},{token:"string",regex:"'|$",next:"start"},{defaultToken:"string"}]}};r.inherits(s,i),t.PythonHighlightRules=s}),ace.define("ace/mode/folding/pythonic",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=t.FoldMode=function(e){this.foldingStartMarker=new RegExp("([\\[{])(?:\\s*)$|("+e+")(?:\\s*)(?:#.*)?$")};r.inherits(s,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=e.getLine(n),i=r.match(this.foldingStartMarker);if(i)return i[1]?this.openingBracketBlock(e,i[1],n,i.index):i[2]?this.indentationBlock(e,n,i.index+i[2].length):this.indentationBlock(e,n)}}.call(s.prototype)}),ace.define("ace/mode/python",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/python_highlight_rules","ace/mode/folding/pythonic","ace/range"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./python_highlight_rules").PythonHighlightRules,o=e("./folding/pythonic").FoldMode,u=e("../range").Range,a=function(){this.HighlightRules=s,this.foldingRules=new o("\\:"),this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart="#",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"){var o=t.match(/^.*[\{\(\[:]\s*$/);o&&(r+=n)}return r};var e={pass:1,"return":1,raise:1,"break":1,"continue":1};this.checkOutdent=function(t,n,r){if(r!=="\r\n"&&r!=="\r"&&r!=="\n")return!1;var i=this.getTokenizer().getLineTokens(n.trim(),t).tokens;if(!i)return!1;do var s=i.pop();while(s&&(s.type=="comment"||s.type=="text"&&s.value.match(/^\s+$/)));return s?s.type=="keyword"&&e[s.value]:!1},this.autoOutdent=function(e,t,n){n+=1;var r=this.$getIndent(t.getLine(n)),i=t.getTabString();r.slice(-i.length)==i&&t.remove(new u(n,r.length-i.length,n,r.length))},this.$id="ace/mode/python"}.call(a.prototype),t.Mode=a})

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
ace.define("ace/snippets/handlebars",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="handlebars"})

View File

@ -1 +0,0 @@
ace.define("ace/snippets/json",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="json"})

View File

@ -1 +0,0 @@
ace.define("ace/snippets/properties",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="properties"})

View File

@ -1 +0,0 @@
ace.define("ace/snippets/swift",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="swift"})

View File

@ -1 +0,0 @@
ace.define("ace/snippets/text",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="text"})

View File

@ -1 +0,0 @@
ace.define("ace/snippets/yaml",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="yaml"})

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

26
jsdoc.json Normal file
View File

@ -0,0 +1,26 @@
{
"opts": {
"template": "./node_modules/jsdoc-nr-template",
"destination": "./docs",
"recurse": true
},
"tags": {
"allowUnknownTags": false,
"dictionaries": ["jsdoc"]
},
"source": {
"_include": [
"./packages/node_modules/@node-red/runtime/lib/api"
]
},
"templates": {
"systemName": "Node-RED Runtime API",
"theme":"yeti",
"footer": "",
"copyright": "Released under the Apache License v2.0",
"default": {
"outputSourceFiles": false
}
},
"plugins": ["plugins/markdown"]
}

1
lib/.gitignore vendored
View File

@ -1 +0,0 @@
*

View File

@ -1,134 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var yaml = require("js-yaml");
/**
* Custom Mustache Context capable to resolve message property and node
* flow and global context
*/
function NodeContext(msg, nodeContext, parent, escapeStrings) {
this.msgContext = new mustache.Context(msg,parent);
this.nodeContext = nodeContext;
this.escapeStrings = escapeStrings;
}
NodeContext.prototype = new mustache.Context();
NodeContext.prototype.lookup = function (name) {
// try message first:
try {
var value = this.msgContext.lookup(name);
if (value !== undefined) {
if (this.escapeStrings && typeof value === "string") {
value = value.replace(/\\/g, "\\\\");
value = value.replace(/\n/g, "\\n");
value = value.replace(/\t/g, "\\t");
value = value.replace(/\r/g, "\\r");
value = value.replace(/\f/g, "\\f");
value = value.replace(/[\b]/g, "\\b");
}
return value;
}
// try node context:
var dot = name.indexOf(".");
/* istanbul ignore else */
if (dot > 0) {
var contextName = name.substr(0, dot);
var variableName = name.substr(dot + 1);
if (contextName === "flow" && this.nodeContext.flow) {
return this.nodeContext.flow.get(variableName);
}
else if (contextName === "global" && this.nodeContext.global) {
return this.nodeContext.global.get(variableName);
}
}
}
catch(err) {
throw err;
}
}
NodeContext.prototype.push = function push (view) {
return new NodeContext(view, this.nodeContext,this.msgContext);
};
function TemplateNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.field = n.field || "payload";
this.template = n.template;
this.syntax = n.syntax || "mustache";
this.fieldType = n.fieldType || "msg";
this.outputFormat = n.output || "str";
var node = this;
node.on("input", function(msg) {
try {
var value;
/***
* Allow template contents to be defined externally
* through inbound msg.template IFF node.template empty
*/
var template = node.template;
if (msg.hasOwnProperty("template")) {
if (template == "" || template === null) {
template = msg.template;
}
}
if (node.syntax === "mustache") {
if (node.outputFormat === "json") {
value = mustache.render(template,new NodeContext(msg, node.context(), null, true));
} else {
value = mustache.render(template,new NodeContext(msg, node.context(), null, false));
}
} else {
value = template;
}
/* istanbul ignore else */
if (node.outputFormat === "json") {
value = JSON.parse(value);
}
/* istanbul ignore else */
if (node.outputFormat === "yaml") {
value = yaml.load(value);
}
if (node.fieldType === 'msg') {
RED.util.setMessageProperty(msg,node.field,value);
} else if (node.fieldType === 'flow') {
node.context().flow.set(node.field,value);
} else if (node.fieldType === 'global') {
node.context().global.set(node.field,value);
}
node.send(msg);
}
catch(err) {
node.error(err.message);
}
});
}
RED.nodes.registerType("template",TemplateNode);
RED.library.register("templates");
}

View File

@ -1,159 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var mqtt = require("./mqtt");
var settings = require(process.env.NODE_RED_HOME+"/red/red").settings;
util.log("[warn] nodes/core/io/lib/mqttConnectionPool.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
var connections = {};
function matchTopic(ts,t) {
if (ts == "#") {
return true;
}
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
return re.test(t);
}
module.exports = {
get: function(broker,port,clientid,username,password,will) {
var id = "["+(username||"")+":"+(password||"")+"]["+(clientid||"")+"]@"+broker+":"+port;
if (!connections[id]) {
connections[id] = function() {
var uid = (1+Math.random()*4294967295).toString(16);
var client = mqtt.createClient(port,broker);
client.uid = uid;
client.setMaxListeners(0);
var options = {keepalive:15};
options.clientId = clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
options.username = username;
options.password = password;
options.will = will;
var queue = [];
var subscriptions = {};
var connecting = false;
var obj = {
_instances: 0,
publish: function(msg) {
if (client.isConnected()) {
client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
} else {
if (!connecting) {
connecting = true;
client.connect(options);
}
queue.push(msg);
}
},
subscribe: function(topic,qos,callback,ref) {
ref = ref||0;
subscriptions[topic] = subscriptions[topic]||{};
var sub = {
topic:topic,
qos:qos,
handler:function(mtopic,mpayload,mqos,mretain) {
if (matchTopic(topic,mtopic)) {
callback(mtopic,mpayload,mqos,mretain);
}
},
ref: ref
};
subscriptions[topic][ref] = sub;
client.on('message',sub.handler);
if (client.isConnected()) {
client.subscribe(topic,qos);
}
},
unsubscribe: function(topic,ref) {
ref = ref||0;
var sub = subscriptions[topic];
if (sub) {
if (sub[ref]) {
client.removeListener('message',sub[ref].handler);
delete sub[ref];
}
if (Object.keys(sub).length == 0) {
delete subscriptions[topic];
client.unsubscribe(topic);
}
}
},
on: function(a,b){
client.on(a,b);
},
once: function(a,b){
client.once(a,b);
},
connect: function() {
if (client && !client.isConnected() && !connecting) {
connecting = true;
client.connect(options);
}
},
disconnect: function(ref) {
this._instances -= 1;
if (this._instances == 0) {
client.disconnect();
client = null;
delete connections[id];
}
},
isConnected: function() {
return client.isConnected();
}
};
client.on('connect',function() {
if (client) {
util.log('[mqtt] ['+uid+'] connected to broker tcp://'+broker+':'+port);
connecting = false;
for (var s in subscriptions) {
var topic = s;
var qos = 0;
for (var r in subscriptions[s]) {
qos = Math.max(qos,subscriptions[s][r].qos);
}
client.subscribe(topic,qos);
}
//console.log("connected - publishing",queue.length,"messages");
while(queue.length) {
var msg = queue.shift();
//console.log(msg);
client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
}
}
});
client.on('connectionlost', function(err) {
util.log('[mqtt] ['+uid+'] connection lost to broker tcp://'+broker+':'+port);
connecting = false;
setTimeout(function() {
obj.connect();
}, settings.mqttReconnectTime||5000);
});
client.on('disconnect', function() {
connecting = false;
util.log('[mqtt] ['+uid+'] disconnected from broker tcp://'+broker+':'+port);
});
return obj
}();
}
connections[id]._instances += 1;
return connections[id];
}
};

View File

@ -1,118 +1,117 @@
{ {
"name": "node-red", "name": "node-red",
"version": "0.18.7", "version": "0.20.0-alpha.0",
"description": "A visual tool for wiring the Internet of Things", "description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
},
"main": "red/red.js",
"scripts": {
"start": "node red.js",
"test": "grunt",
"build": "grunt build"
},
"bin": {
"node-red": "./red.js",
"node-red-pi": "bin/node-red-pi"
},
"contributors": [
{
"name": "Nick O'Leary"
}, },
{ "private": "true",
"name": "Dave Conway-Jones" "scripts": {
"start": "node packages/node_modules/node-red/red.js",
"test": "grunt",
"build": "grunt build",
"docs": "grunt docs"
},
"contributors": [
{
"name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
],
"dependencies": {
"ajv": "6.5.3",
"basic-auth": "2.0.0",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"cheerio": "0.22.0",
"clone": "2.1.2",
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors": "2.8.4",
"cron": "1.4.1",
"denque": "1.3.0",
"express": "4.16.3",
"express-session": "1.15.6",
"fs-extra": "5.0.0",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1",
"i18next": "11.6.0",
"is-utf8": "0.2.1",
"js-yaml": "3.12.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.5.4",
"media-typer": "0.3.0",
"memorystore": "1.6.0",
"mime": "1.4.1",
"mqtt": "2.18.8",
"multer": "1.3.1",
"mustache": "2.3.2",
"node-red-node-email": "0.1.*",
"node-red-node-feedparser": "^0.1.12",
"node-red-node-rbe": "0.2.*",
"node-red-node-twitter": "^1.1.0",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.1",
"passport": "0.4.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.3.3",
"request": "2.88.0",
"semver": "5.5.1",
"sentiment": "2.1.0",
"uglify-js": "3.4.9",
"when": "3.7.8",
"ws": "1.1.5",
"xml2js": "0.4.19"
},
"optionalDependencies": {
"bcrypt": "~2.0.0"
},
"devDependencies": {
"chromedriver": "^2.41.0",
"grunt": "~1.0.3",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.1",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~3.4.0",
"grunt-contrib-watch": "~1.1.0",
"grunt-jsdoc": "^2.2.1",
"grunt-jsdoc-to-markdown": "^4.0.0",
"grunt-jsonlint": "~1.1.0",
"grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon": "~0.4.2",
"grunt-sass": "~2.0.0",
"grunt-simple-mocha": "~0.4.1",
"grunt-webdriver": "^2.0.3",
"http-proxy": "^1.16.2",
"istanbul": "0.4.5",
"minami": "1.2.3",
"mocha": "^5.2.0",
"mosca": "^2.8.3",
"should": "^8.4.0",
"sinon": "1.17.7",
"stoppable": "^1.0.6",
"supertest": "3.1.0",
"wdio-chromedriver-service": "^0.1.3",
"wdio-mocha-framework": "^0.6.2",
"wdio-spec-reporter": "^0.1.5",
"webdriverio": "^4.13.1",
"node-red-node-test-helper": "node-red/node-red-node-test-helper",
"jsdoc-nr-template": "node-red/jsdoc-nr-template"
},
"engines": {
"node": ">=8"
} }
],
"keywords": [
"editor",
"messaging",
"iot",
"flow"
],
"dependencies": {
"ajv": "6.5.1",
"basic-auth": "2.0.0",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"cheerio": "0.22.0",
"clone": "2.1.1",
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors": "2.8.4",
"cron": "1.3.0",
"express": "4.16.3",
"express-session": "1.15.6",
"fs-extra": "5.0.0",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"i18next": "1.10.6",
"is-utf8": "0.2.1",
"js-yaml": "3.11.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.5.4",
"media-typer": "0.3.0",
"memorystore": "1.6.0",
"mqtt": "2.18.0",
"multer": "1.3.0",
"mustache": "2.3.0",
"node-red-node-email": "0.1.*",
"node-red-node-feedparser": "0.1.*",
"node-red-node-rbe": "0.2.*",
"node-red-node-twitter": "*",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.1",
"passport": "0.4.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.3.3",
"request": "2.87.0",
"semver": "5.5.0",
"sentiment": "2.1.0",
"uglify-js": "3.3.25",
"when": "3.7.8",
"ws": "1.1.5",
"xml2js": "0.4.19"
},
"optionalDependencies": {
"bcrypt": "~1.0.3"
},
"devDependencies": {
"chromedriver": "^2.33.2",
"grunt": "~1.0.1",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.2.0",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~3.3.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-jsonlint": "~1.1.0",
"grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon": "~0.4.2",
"grunt-sass": "~2.0.0",
"grunt-simple-mocha": "~0.4.1",
"grunt-webdriver": "^2.0.3",
"http-proxy": "^1.16.2",
"istanbul": "0.4.5",
"mocha": "^5.1.1",
"should": "^8.4.0",
"sinon": "1.17.7",
"stoppable": "^1.0.6",
"supertest": "3.0.0",
"wdio-chromedriver-service": "^0.1.1",
"wdio-mocha-framework": "^0.5.11",
"wdio-spec-reporter": "^0.1.3",
"webdriverio": "^4.9.11",
"node-red-node-test-helper": "^0.1.7"
},
"engines": {
"node": ">=4"
}
} }

View File

@ -0,0 +1,178 @@
Copyright JS Foundation and other contributors, http://js.foundation
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,12 @@
@node-red/editor-api
====================
Node-RED editor api module.
This provides an Express application that can be used to serve the Node-RED
editor.
### Source
The main Node-RED modules are maintained as a monorepo on [GitHub](https://github.com/node-red/node-red).

View File

@ -0,0 +1,41 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var opts = {
user: req.user,
scope: req.params.scope,
id: req.params.id,
key: req.params[0],
store: req.query['store']
}
runtimeAPI.context.getValue(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var runtimeAPI;
var apiUtils = require("../util");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.flows.getFlow(opts).then(function(result) {
return res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
post: function(req,res) {
var opts = {
user: req.user,
flow: req.body
}
runtimeAPI.flows.addFlow(opts).then(function(id) {
return res.json({id:id});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
put: function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
flow: req.body
}
runtimeAPI.flows.updateFlow(opts).then(function(id) {
return res.json({id:id});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
delete: function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.flows.deleteFlow(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var runtimeAPI;
var apiUtils = require("../util");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) {
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
}
var opts = {
user: req.user
}
runtimeAPI.flows.getFlows(opts).then(function(result) {
if (version === "v1") {
res.json(result.flows);
} else if (version === "v2") {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
post: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) {
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
}
var opts = {
user: req.user,
deploymentType: req.get("Node-RED-Deployment-Type")||"full"
}
if (opts.deploymentType !== 'reload') {
if (version === "v1") {
opts.flows = {flows: req.body}
} else {
opts.flows = req.body;
}
}
runtimeAPI.flows.setFlows(opts).then(function(result) {
if (version === "v1") {
res.status(204).end();
} else {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@ -25,11 +25,11 @@ var auth = require("../auth");
var apiUtil = require("../util"); var apiUtil = require("../util");
module.exports = { module.exports = {
init: function(runtime) { init: function(runtimeAPI) {
flows.init(runtime); flows.init(runtimeAPI);
flow.init(runtime); flow.init(runtimeAPI);
nodes.init(runtime); nodes.init(runtimeAPI);
context.init(runtime); context.init(runtimeAPI);
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;
@ -48,6 +48,8 @@ module.exports = {
// Nodes // Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler); adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler); adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
adminApp.get(/\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler); adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler); adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler);
adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler); adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler);

View File

@ -0,0 +1,173 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
getAll: function(req,res) {
var opts = {
user: req.user
}
if (req.get("accept") == "application/json") {
runtimeAPI.nodes.getNodeList(opts).then(function(list) {
res.json(list);
})
} else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) {
res.send(configs);
})
}
},
post: function(req,res) {
var opts = {
user: req.user,
module: req.body.module,
version: req.body.version
}
runtimeAPI.nodes.addModule(opts).then(function(info) {
res.json(info);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
delete: function(req,res) {
var opts = {
user: req.user,
module: req.params[0]
}
runtimeAPI.nodes.removeModule(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
getSet: function(req,res) {
var opts = {
user: req.user,
id: req.params[0] + "/" + req.params[2]
}
if (req.get("accept") === "application/json") {
runtimeAPI.nodes.getNodeInfo(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
return res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
},
getModule: function(req,res) {
var opts = {
user: req.user,
module: req.params[0]
}
runtimeAPI.nodes.getModuleInfo(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
putSet: function(req,res) {
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({code:"invalid_request", message:"Invalid request"});
return;
}
var opts = {
user: req.user,
id: req.params[0] + "/" + req.params[2],
enabled: body.enabled
}
runtimeAPI.nodes.setNodeSetState(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
putModule: function(req,res) {
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({code:"invalid_request", message:"Invalid request"});
return;
}
var opts = {
user: req.user,
module: req.params[0],
enabled: body.enabled
}
runtimeAPI.nodes.setModuleState(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
getModuleCatalog: function(req,res) {
var opts = {
user: req.user,
module: req.params[0],
lang: req.query.lng
}
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},
getModuleCatalogs: function(req,res) {
var opts = {
user: req.user,
lang: req.query.lng
}
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},
getIcons: function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.nodes.getIconList(opts).then(function(list) {
res.json(list);
});
}
};

View File

@ -25,7 +25,7 @@ var permissions = require("./permissions");
var theme = require("../editor/theme"); var theme = require("../editor/theme");
var settings = null; var settings = null;
var log = null var log = require("@node-red/util").log; // TODO: separate module
passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.bearerStrategy.BearerStrategy);
@ -36,14 +36,12 @@ var server = oauth2orize.createServer();
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange)); server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
function init(runtime) { function init(_settings,storage) {
settings = runtime.settings; settings = _settings;
log = runtime.log;
if (settings.adminAuth) { if (settings.adminAuth) {
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module); var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
Users.init(mergedAdminAuth); Users.init(mergedAdminAuth);
Tokens.init(mergedAdminAuth,runtime.storage); Tokens.init(mergedAdminAuth,runtime.storage);
strategies.init(runtime);
} }
} }

View File

@ -26,7 +26,7 @@ var Users = require("./users");
var Clients = require("./clients"); var Clients = require("./clients");
var permissions = require("./permissions"); var permissions = require("./permissions");
var log; var log = require("@node-red/util").log; // TODO: separate module
var bearerStrategy = function (accessToken, done) { var bearerStrategy = function (accessToken, done) {
// is this a valid token? // is this a valid token?
@ -124,9 +124,6 @@ AnonymousStrategy.prototype.authenticate = function(req) {
} }
module.exports = { module.exports = {
init: function(runtime) {
log = runtime.log;
},
bearerStrategy: bearerStrategy, bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy, clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange, passwordTokenExchange: passwordTokenExchange,

View File

@ -0,0 +1,243 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var ws = require("ws");
var log = require("@node-red/util").log; // TODO: separate module
var Tokens;
var Users;
var Permissions;
var server;
var settings;
var runtimeAPI;
var wsServer;
var activeConnections = [];
var anonymousUser;
var retained = {};
var heartbeatTimer;
var lastSentTime;
function init(_server,_settings,_runtimeAPI) {
server = _server;
settings = _settings;
runtimeAPI = _runtimeAPI;
Tokens = require("../auth/tokens");
Users = require("../auth/users");
Permissions = require("../auth/permissions");
}
function generateSession(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = [];
for (var i=0;i<length;i++) {
token.push(c[Math.floor(Math.random()*c.length)]);
}
return token.join("");
}
function CommsConnection(ws) {
this.session = generateSession(32);
this.ws = ws;
this.stack = [];
this.user = null;
this.lastSentTime = 0;
var self = this;
log.audit({event: "comms.open"});
log.trace("comms.open "+self.session);
var pendingAuth = (settings.adminAuth != null);
if (!pendingAuth) {
addActiveConnection(self);
}
ws.on('close',function() {
log.audit({event: "comms.close",user:self.user, session: self.session});
log.trace("comms.close "+self.session);
removeActiveConnection(self);
});
ws.on('message', function(data,flags) {
var msg = null;
try {
msg = JSON.parse(data);
} catch(err) {
log.trace("comms received malformed message : "+err.toString());
return;
}
if (!pendingAuth) {
if (msg.subscribe) {
self.subscribe(msg.subscribe);
// handleRemoteSubscription(ws,msg.subscribe);
}
} else {
var completeConnection = function(userScope,sendAck) {
try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"}));
ws.close();
} else {
pendingAuth = false;
addActiveConnection(self);
if (sendAck) {
ws.send(JSON.stringify({auth:"ok"}));
}
}
} catch(err) {
console.log(err.stack);
// Just in case the socket closes before we attempt
// to send anything.
}
}
if (msg.auth) {
Tokens.get(msg.auth).then(function(client) {
if (client) {
Users.get(client.user).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(client.scope,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
});
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
});
} else {
if (anonymousUser) {
log.audit({event: "comms.auth",user:anonymousUser});
self.user = anonymousUser;
completeConnection(anonymousUser.permissions,false);
//TODO: duplicated code - pull non-auth message handling out
if (msg.subscribe) {
self.subscribe(msg.subscribe);
}
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
}
}
});
ws.on('error', function(err) {
log.warn(log._("comms.error",{message:err.toString()}));
});
}
CommsConnection.prototype.send = function(topic,data) {
var self = this;
if (topic && data) {
this.stack.push({topic:topic,data:data});
}
if (!this._xmitTimer) {
this._xmitTimer = setTimeout(function() {
try {
self.ws.send(JSON.stringify(self.stack));
self.lastSentTime = Date.now();
} catch(err) {
removeActiveConnection(self);
log.warn(log._("comms.error-send",{message:err.toString()}));
}
delete self._xmitTimer;
self.stack = [];
},50);
}
}
CommsConnection.prototype.subscribe = function(topic) {
runtimeAPI.comms.subscribe({
user: this.user,
client: this,
topic: topic
})
}
function start() {
if (!settings.disableEditor) {
Users.default().then(function(_anonymousUser) {
anonymousUser = _anonymousUser;
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({
server:server,
path:path,
// Disable the deflate option due to this issue
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
//perMessageDeflate: false
});
wsServer.on('connection',function(ws) {
var commsConnection = new CommsConnection(ws);
});
wsServer.on('error', function(err) {
log.warn(log._("comms.error-server",{message:err.toString()}));
});
lastSentTime = Date.now();
heartbeatTimer = setInterval(function() {
var now = Date.now();
if (now-lastSentTime > webSocketKeepAliveTime) {
activeConnections.forEach(connection => connection.send("hb",lastSentTime));
}
}, webSocketKeepAliveTime);
});
}
}
function stop() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
if (wsServer) {
wsServer.close();
wsServer = null;
}
}
function addActiveConnection(connection) {
activeConnections.push(connection);
runtimeAPI.comms.addConnection({client: connection});
}
function removeActiveConnection(connection) {
for (var i=0;i<activeConnections.length;i++) {
if (activeConnections[i] === connection) {
activeConnections.splice(i,1);
runtimeAPI.comms.removeConnection({client:connection})
break;
}
}
}
module.exports = {
init:init,
start:start,
stop:stop
}

View File

@ -0,0 +1,36 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var runtimeAPI;
var apiUtils = require("../util");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI
},
get: function (req, res) {
var opts = {
user: req.user,
type: req.params.type,
id: req.params.id
}
runtimeAPI.flows.getNodeCredentials(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@ -24,31 +24,35 @@ var info = require("./settings");
var auth = require("../auth"); var auth = require("../auth");
var nodes = require("../admin/nodes"); // TODO: move /icons into here var nodes = require("../admin/nodes"); // TODO: move /icons into here
var needsPermission; var needsPermission;
var runtime; var runtimeAPI;
var log; var log = require("@node-red/util").log; // TODO: separate module
var i18n = require("@node-red/util").i18n; // TODO: separate module
var apiUtil = require("../util"); var apiUtil = require("../util");
var ensureRuntimeStarted = function(req,res,next) { var ensureRuntimeStarted = function(req,res,next) {
if (!runtime.isStarted()) { runtimeAPI.isStarted().then( started => {
log.error("Node-RED runtime not started"); if (!started) {
res.status(503).send("Not started"); log.error("Node-RED runtime not started");
} else { res.status(503).send("Not started");
next(); } else {
} next()
}
})
} }
module.exports = { module.exports = {
init: function(server, _runtime) { init: function(server, settings, _runtimeAPI) {
runtime = _runtime; runtimeAPI = _runtimeAPI;
log = runtime.log;
needsPermission = auth.needsPermission; needsPermission = auth.needsPermission;
var settings = runtime.settings;
if (!settings.disableEditor) { if (!settings.disableEditor) {
info.init(runtime); info.init(runtimeAPI);
comms.init(server,runtime); comms.init(server,settings,runtimeAPI);
var ui = require("./ui"); var ui = require("./ui");
ui.init(runtime);
ui.init(runtimeAPI);
var editorApp = express(); var editorApp = express();
if (settings.requireHttps === true) { if (settings.requireHttps === true) {
editorApp.enable('trust proxy'); editorApp.enable('trust proxy');
@ -67,31 +71,31 @@ module.exports = {
editorApp.get("/icons/:scope/:module/:icon",ui.icon); editorApp.get("/icons/:scope/:module/:icon",ui.icon);
var theme = require("./theme"); var theme = require("./theme");
theme.init(runtime); theme.init(settings);
editorApp.use("/theme",theme.app()); editorApp.use("/theme",theme.app());
editorApp.use("/",ui.editorResources); editorApp.use("/",ui.editorResources);
//Projects //Projects
var projects = require("./projects"); var projects = require("./projects");
projects.init(runtime); projects.init(runtimeAPI);
editorApp.use("/projects",projects.app()); editorApp.use("/projects",projects.app());
// Locales // Locales
var locales = require("./locales"); var locales = require("./locales");
locales.init(runtime); locales.init(runtimeAPI);
editorApp.get('/locales/nodes',locales.getAllNodes,apiUtil.errorHandler);
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler); editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
// Library // Library
var library = require("./library"); var library = require("./library");
library.init(editorApp,runtime); library.init(runtimeAPI);
editorApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,apiUtil.errorHandler);
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler); editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
editorApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,apiUtil.errorHandler); editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
// Credentials // Credentials
var credentials = require("./credentials"); var credentials = require("./credentials");
credentials.init(runtime); credentials.init(runtimeAPI);
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler); editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
// Settings // Settings
@ -100,18 +104,15 @@ module.exports = {
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler); editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
// User Settings // User Settings
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler); editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
// SSH keys // SSH keys
var sshkeys = require("./sshkeys"); editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys());
sshkeys.init(runtime);
editorApp.use("/settings/user/keys",sshkeys.app());
return editorApp; return editorApp;
} }
}, },
start: function() { start: function() {
var catalogPath = path.resolve(path.join(__dirname,"locales")); var catalogPath = path.resolve(path.join(path.dirname(require.resolve("@node-red/editor-client")),"locales"));
return runtime.i18n.registerMessageCatalogs([ return i18n.registerMessageCatalogs([
{namespace: "editor", dir: catalogPath, file:"editor.json"}, {namespace: "editor", dir: catalogPath, file:"editor.json"},
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"}, {namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
{namespace: "infotips", dir: catalogPath, file:"infotips.json"} {namespace: "infotips", dir: catalogPath, file:"infotips.json"}
@ -119,7 +120,5 @@ module.exports = {
comms.start(); comms.start();
}); });
}, },
stop: comms.stop, stop: comms.stop
publish: comms.publish,
registerLibrary: library.register
} }

View File

@ -0,0 +1,83 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var fs = require('fs');
var fspath = require('path');
var when = require('when');
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
getAll: function(req,res) {
var opts = {
user: req.user,
type: 'flows'
}
runtimeAPI.library.getEntries(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
},
getEntry: function(req,res) {
var opts = {
user: req.user,
type: req.params[0],
path: req.params[1]||""
}
runtimeAPI.library.getEntry(opts).then(function(result) {
if (typeof result === "string") {
if (opts.type === 'flows') {
res.writeHead(200, {'Content-Type': 'application/json'});
} else {
res.writeHead(200, {'Content-Type': 'text/plain'});
}
res.write(result);
res.end();
} else {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
},
saveEntry: function(req,res) {
var opts = {
user: req.user,
type: req.params[0],
path: req.params[1]||""
}
// TODO: horrible inconsistencies between flows and all other types
if (opts.type === "flows") {
opts.meta = {};
opts.body = JSON.stringify(req.body);
} else {
opts.meta = req.body;
opts.body = opts.meta.text;
delete opts.meta.text;
}
runtimeAPI.library.saveEntry(opts).then(function(result) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
}
}

View File

@ -16,37 +16,38 @@
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
//var apiUtil = require('../util'); //var apiUtil = require('../util');
var i18n;
var redNodes; var i18n = require("@node-red/util").i18n; // TODO: separate module
var runtimeAPI;
function loadResource(lang, namespace) {
var catalog = i18n.i.getResourceBundle(lang, namespace);
if (!catalog) {
var parts = lang.split("-");
if (parts.length == 2) {
var new_lang = parts[0];
return i18n.i.getResourceBundle(new_lang, namespace);
}
}
return catalog;
}
module.exports = { module.exports = {
init: function(runtime) { init: function(_runtimeAPI) {
i18n = runtime.i18n; runtimeAPI = _runtimeAPI;
redNodes = runtime.nodes;
}, },
get: function(req,res) { get: function(req,res) {
var namespace = req.params[0]; var namespace = req.params[0];
var lngs = req.query.lng; var lngs = req.query.lng;
namespace = namespace.replace(/\.json$/,""); namespace = namespace.replace(/\.json$/,"");
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []); var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
var prevLang = i18n.i.lng(); var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default // Trigger a load from disk of the language if it is not the default
i18n.i.setLng(lang, function(){ i18n.i.changeLanguage(lang, function(){
var catalog = i18n.catalog(namespace,lang); var catalog = loadResource(lang, namespace);
res.json(catalog||{}); res.json(catalog||{});
}); });
i18n.i.setLng(prevLang); i18n.i.changeLanguage(prevLang);
},
getAllNodes: function(req,res) {
var lngs = req.query.lng;
var nodeList = redNodes.getNodeList();
var result = {};
nodeList.forEach(function(n) {
if (n.module !== "node-red") {
result[n.id] = i18n.catalog(n.id,lngs)||{};
}
});
res.json(result);
} }
} }

View File

@ -0,0 +1,511 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var apiUtils = require("../util");
var runtimeAPI;
var needsPermission = require("../auth").needsPermission;
function listProjects(req,res) {
var opts = {
user: req.user
}
runtimeAPI.projects.listProjects(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
}
function getProject(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getProject(opts).then(function(data) {
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
function getProjectStatus(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.query.remote
}
runtimeAPI.projects.getStatus(opts).then(function(data){
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
function getProjectRemotes(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getRemotes(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
app: function() {
var app = express();
app.use(function(req,res,next) {
runtimeAPI.projects.available().then(function(available) {
if (!available) {
res.status(404).end();
} else {
next();
}
})
});
// Projects
// List all projects
app.get("/", needsPermission("projects.read"),listProjects);
// Create project
app.post("/", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
project: req.body
}
runtimeAPI.projects.createProject(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Update a project
app.put("/:id", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
project: req.body
}
if (req.body.active) {
runtimeAPI.projects.setActiveProject(opts).then(function() {
listProjects(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else if (req.body.initialise) {
runtimeAPI.projects.initialiseProject(opts).then(function() {
getProject(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else if (req.body.hasOwnProperty('credentialSecret') ||
req.body.hasOwnProperty('description') ||
req.body.hasOwnProperty('dependencies')||
req.body.hasOwnProperty('summary') ||
req.body.hasOwnProperty('files') ||
req.body.hasOwnProperty('git')) {
runtimeAPI.projects.updateProject(opts).then(function() {
getProject(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else {
res.status(400).json({error:"unexpected_error", message:"invalid_request"});
}
});
// Get project metadata
app.get("/:id", needsPermission("projects.read"), getProject);
// Delete project
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.deleteProject(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get project status - files, commit counts, branch info
app.get("/:id/status", needsPermission("projects.read"), getProjectStatus);
// Project file listing
app.get("/:id/files", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getFiles(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get file content in a given tree (index/stage)
app.get("/:id/files/:treeish/*", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0],
tree: req.params.treeish
}
runtimeAPI.projects.getFile(opts).then(function(data) {
res.json({content:data});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Revert a file
app.delete("/:id/files/_/*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0]
}
runtimeAPI.projects.revertFile(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Stage a file
app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0]
}
runtimeAPI.projects.stageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Stage multiple files
app.post("/:id/stage", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.body.files
}
runtimeAPI.projects.stageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Commit changes
app.post("/:id/commit", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
message: req.body.message
}
runtimeAPI.projects.commit(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Unstage a file
app.delete("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0]
}
runtimeAPI.projects.unstageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Unstage multiple files
app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.unstageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a file diff
app.get("/:id/diff/:type/*", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0],
type: req.params.type
}
runtimeAPI.projects.getFileDiff(opts).then(function(data) {
res.json({
diff: data
})
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of commits
app.get("/:id/commits", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
limit: req.query.limit || 20,
before: req.query.before
}
runtimeAPI.projects.getCommits(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get an individual commit details
app.get("/:id/commits/:sha", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
sha: req.params.sha
}
runtimeAPI.projects.getCommit(opts).then(function(data) {
res.json({commit:data});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Push local commits to remote
app.post("/:id/push/?*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.params[0],
track: req.query.u
}
runtimeAPI.projects.push(opts).then(function(data) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Pull remote commits
app.post("/:id/pull/?*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.params[0],
track: req.query.setUpstream,
allowUnrelatedHistories: req.query.allowUnrelatedHistories
}
runtimeAPI.projects.pull(opts).then(function(data) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Abort an ongoing merge
app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.abortMerge(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Resolve a merge
app.post("/:id/resolve/*", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0],
resolution: req.body.resolutions
}
runtimeAPI.projects.resolveMerge(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of local branches
app.get("/:id/branches", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
remote: false
}
runtimeAPI.projects.getBranches(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Delete a local branch - ?force=true
app.delete("/:id/branches/:branchName", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
branch: req.params.branchName,
force: !!req.query.force
}
runtimeAPI.projects.deleteBranch(opts).then(function(data) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of remote branches
app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
remote: true
}
runtimeAPI.projects.getBranches(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get branch status - commit counts/ahead/behind
app.get("/:id/branches/remote/*/status", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
branch: req.params[0]
}
runtimeAPI.projects.getBranchStatus(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Set the active local branch
app.post("/:id/branches", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
branch: req.body.name,
create: req.body.create
}
runtimeAPI.projects.setBranch(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of remotes
app.get("/:id/remotes", needsPermission("projects.read"), getProjectRemotes);
// Add a remote
app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.body
}
if (/^https?:\/\/[^/]+@/i.test(req.body.url)) {
res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"});
return;
}
runtimeAPI.projects.addRemote(opts).then(function(data) {
getProjectRemotes(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Delete a remote
app.delete("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.params.remoteName
}
runtimeAPI.projects.removeRemote(opts).then(function(data) {
getProjectRemotes(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Update a remote
app.put("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req,res) {
var remote = req.body || {};
remote.name = req.params.remoteName;
var opts = {
user: req.user,
id: req.params.id,
remote: remote
}
runtimeAPI.projects.updateRemote(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
return app;
}
}

View File

@ -0,0 +1,60 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
var sshkeys = require("./sshkeys");
var theme = require("./theme");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
sshkeys.init(runtimeAPI);
},
runtimeSettings: function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
var themeSettings = theme.settings();
if (themeSettings) {
result.editorTheme = themeSettings;
}
res.json(result);
});
},
userSettings: function(req, res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getUserSettings(opts).then(function(result) {
res.json(result);
});
},
updateUserSettings: function(req,res) {
var opts = {
user: req.user,
settings: req.body
}
runtimeAPI.settings.updateUserSettings(opts).then(function(result) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
},
sshkeys: function() {
return sshkeys.app()
}
}

View File

@ -0,0 +1,101 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var express = require("express");
var runtimeAPI;
function getUsername(userObj) {
var username = '__default';
if ( userObj && userObj.name ) {
username = userObj.name;
}
return username;
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
app: function() {
var app = express();
// List all SSH keys
app.get("/", function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getUserKeys(opts).then(function(list) {
res.json({
keys: list
});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Get SSH key detail
app.get("/:id", function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.settings.getUserKey(opts).then(function(data) {
res.json({
publickey: data
});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Generate a SSH key
app.post("/", function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
// TODO: validate params
opts.name = req.body.name;
opts.password = req.body.password;
opts.comment = req.body.comment;
opts.size = req.body.size;
runtimeAPI.settings.generateUserKey(opts).then(function(name) {
res.json({
name: name
});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Delete a SSH key
app.delete("/:id", function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.settings.removeUserKey(opts).then(function(name) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
return app;
}
}

View File

@ -40,7 +40,6 @@ var defaultContext = {
var theme = null; var theme = null;
var themeContext = clone(defaultContext); var themeContext = clone(defaultContext);
var themeSettings = null; var themeSettings = null;
var runtime = null;
var themeApp; var themeApp;
@ -78,12 +77,8 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
} }
module.exports = { module.exports = {
init: function(runtime) { init: function(settings) {
var settings = runtime.settings;
themeContext = clone(defaultContext); themeContext = clone(defaultContext);
if (runtime.version) {
themeContext.version = runtime.version();
}
themeSettings = null; themeSettings = null;
theme = settings.editorTheme || {}; theme = settings.editorTheme || {};
}, },

View File

@ -17,18 +17,22 @@ var express = require('express');
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var Mustache = require("mustache"); var Mustache = require("mustache");
var mime = require("mime");
var apiUtils = require("../util");
var theme = require("./theme"); var theme = require("./theme");
var redNodes; var runtimeAPI;
var editorClientDir = path.dirname(require.resolve("@node-red/editor-client"));
var templateDir = path.resolve(__dirname+"/../../../editor/templates"); var defaultNodeIcon = path.join(editorClientDir,"public","red","images","icons","arrow-in.png");
var editorTemplatePath = path.join(editorClientDir,"templates","index.mst");
var editorTemplate; var editorTemplate;
module.exports = { module.exports = {
init: function(runtime) { init: function(_runtimeAPI) {
redNodes = runtime.nodes; runtimeAPI = _runtimeAPI;
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8"); editorTemplate = fs.readFileSync(editorTemplatePath,"utf8");
Mustache.parse(editorTemplate); Mustache.parse(editorTemplate);
}, },
@ -46,11 +50,26 @@ module.exports = {
var icon = req.params.icon; var icon = req.params.icon;
var scope = req.params.scope; var scope = req.params.scope;
var module = scope ? scope + '/' + req.params.module : req.params.module; var module = scope ? scope + '/' + req.params.module : req.params.module;
var iconPath = redNodes.getNodeIconPath(module,icon); var opts = {
res.sendFile(iconPath); user: req.user,
module: module,
icon: icon
}
runtimeAPI.nodes.getIcon(opts).then(function(data) {
if (data) {
var contentType = mime.lookup(icon);
res.set("Content-Type", contentType);
res.send(data);
} else {
res.sendFile(defaultNodeIcon);
}
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
}, },
editor: function(req,res) { editor: function(req,res) {
res.send(Mustache.render(editorTemplate,theme.context())); res.send(Mustache.render(editorTemplate,theme.context()));
}, },
editorResources: express.static(__dirname + '/../../../public') editorResources: express.static(path.join(editorClientDir,'public'))
}; };

View File

@ -26,17 +26,21 @@ var apiUtil = require("./util");
var adminApp; var adminApp;
var server; var server;
var runtime;
var editor; var editor;
function init(_server,_runtime) { function init(_server,settings,storage,runtimeAPI) {
server = _server; server = _server;
runtime = _runtime;
var settings = runtime.settings;
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
apiUtil.init(runtime);
adminApp = express(); adminApp = express();
auth.init(runtime);
var cors = require('cors');
var corsHandler = cors({
origin: "*",
methods: "GET,PUT,POST,DELETE"
});
adminApp.use(corsHandler);
auth.init(settings,storage);
var maxApiRequestSize = settings.apiMaxLength || '5mb'; var maxApiRequestSize = settings.apiMaxLength || '5mb';
adminApp.use(bodyParser.json({limit:maxApiRequestSize})); adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
@ -61,7 +65,7 @@ function init(_server,_runtime) {
// Editor // Editor
if (!settings.disableEditor) { if (!settings.disableEditor) {
editor = require("./editor"); editor = require("./editor");
var editorApp = editor.init(server, runtime); var editorApp = editor.init(server, settings, runtimeAPI);
adminApp.use(editorApp); adminApp.use(editorApp);
} }
@ -70,7 +74,7 @@ function init(_server,_runtime) {
adminApp.use(corsHandler); adminApp.use(corsHandler);
} }
var adminApiApp = require("./admin").init(runtime); var adminApiApp = require("./admin").init(runtimeAPI);
adminApp.use(adminApiApp); adminApp.use(adminApiApp);
} else { } else {
adminApp = null; adminApp = null;
@ -93,23 +97,9 @@ module.exports = {
init: init, init: init,
start: start, start: start,
stop: stop, stop: stop,
library: {
register: function(type) {
if (editor) {
editor.registerLibrary(type);
}
}
},
auth: { auth: {
needsPermission: auth.needsPermission needsPermission: auth.needsPermission
}, },
comms: {
publish: function(topic,data,retain) {
if (editor) {
editor.publish(topic,data,retain);
}
}
},
get adminApp() { return adminApp; }, get adminApp() { return adminApp; },
get server() { return server; } get server() { return server; }
}; };

View File

@ -15,16 +15,12 @@
**/ **/
var i18n; var log = require("@node-red/util").log; // TODO: separate module
var log; var i18n = require("@node-red/util").i18n; // TODO: separate module
module.exports = { module.exports = {
init: function(_runtime) {
log = _runtime.log;
i18n = _runtime.i18n;
},
errorHandler: function(err,req,res,next) { errorHandler: function(err,req,res,next) {
console.error(err.stack);
if (err.message === "request entity too large") { if (err.message === "request entity too large") {
log.error(err); log.error(err);
} else { } else {
@ -41,5 +37,11 @@ module.exports = {
lang = acceptedLanguages[0]; lang = acceptedLanguages[0];
} }
return lang; return lang;
},
rejectHandler: function(req,res,err) {
res.status(err.status||500).json({
code: err.code||"unexpected_error",
message: err.message||err.toString()
});
} }
} }

View File

@ -0,0 +1,33 @@
{
"name": "@node-red/editor",
"version": "0.20.0-alpha.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/node-red/node-red.git"
},
"contributors": [
{ "name": "Nick O'Leary" },
{ "name": "Dave Conway-Jones"}
],
"dependencies": {
"@node-red/util": "*",
"@node-red/editor-client": "*",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"clone": "2.1.2",
"cors": "2.8.4",
"express-session": "1.15.6",
"express": "4.16.3",
"memorystore": "1.6.0",
"mime": "1.4.1",
"mustache": "2.3.2",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0",
"when": "3.7.8",
"ws": "1.1.5"
}
}

View File

@ -0,0 +1 @@
src

View File

@ -0,0 +1,178 @@
Copyright JS Foundation and other contributors, http://js.foundation
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,10 @@
@node-red/editor-client
====================
Node-RED editor resources module.
This provides all of the client-side resources of the Node-RED editor application.
### Source
The main Node-RED modules are maintained as a monorepo on [GitHub](https://github.com/node-red/node-red).

View File

@ -13,4 +13,5 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var RED = {};
module.exports = false

View File

@ -23,6 +23,7 @@
"confirmDelete": "Confirm delete", "confirmDelete": "Confirm delete",
"delete": "Are you sure you want to delete '__label__'?", "delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here", "dropFlowHere": "Drop the flow here",
"addFlow": "Add Flow",
"status": "Status", "status": "Status",
"enabled": "Enabled", "enabled": "Enabled",
"disabled":"Disabled", "disabled":"Disabled",
@ -46,6 +47,9 @@
"sidebar": { "sidebar": {
"show": "Show sidebar" "show": "Show sidebar"
}, },
"palette": {
"show": "Show palette"
},
"settings": "Settings", "settings": "Settings",
"userSettings": "User Settings", "userSettings": "User Settings",
"nodes": "Nodes", "nodes": "Nodes",
@ -78,6 +82,12 @@
"projects-settings": "Project Settings" "projects-settings": "Project Settings"
} }
}, },
"actions": {
"toggle-navigator": "Toggle navigator",
"zoom-out": "Zoom out",
"zoom-reset": "Reset zoom",
"zoom-in": "Zoom in"
},
"user": { "user": {
"loggedInAs": "Logged in as __name__", "loggedInAs": "Logged in as __name__",
"username": "Username", "username": "Username",
@ -97,6 +107,7 @@
"undeployedChanges": "node has undeployed changes", "undeployedChanges": "node has undeployed changes",
"nodeActionDisabled": "node actions disabled within subflow", "nodeActionDisabled": "node actions disabled within subflow",
"missing-types": "<p>Flows stopped due to missing node types.</p>", "missing-types": "<p>Flows stopped due to missing node types.</p>",
"safe-mode":"<p>Flows stopped in safe mode.</p><p>You can modify your flows and deploy the changes to restart.</p>",
"restartRequired": "Node-RED must be restarted to enable upgraded modules", "restartRequired": "Node-RED must be restarted to enable upgraded modules",
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>", "credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
"credentials_load_failed_reset":"<p>Credentials could not be decrypted</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p><p>The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.</p>", "credentials_load_failed_reset":"<p>Credentials could not be decrypted</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p><p>The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.</p>",
@ -172,7 +183,10 @@
"modifiedFlowsDesc": "Only deploys flows that contain changed nodes", "modifiedFlowsDesc": "Only deploys flows that contain changed nodes",
"modifiedNodes": "Modified Nodes", "modifiedNodes": "Modified Nodes",
"modifiedNodesDesc": "Only deploys nodes that have changed", "modifiedNodesDesc": "Only deploys nodes that have changed",
"restartFlows": "Restart Flows",
"restartFlowsDesc": "Restarts the current deployed flows",
"successfulDeploy": "Successfully deployed", "successfulDeploy": "Successfully deployed",
"successfulRestart": "Successfully restarted flows",
"deployFailed": "Deploy failed: __message__", "deployFailed": "Deploy failed: __message__",
"unusedConfigNodes":"You have some unused configuration nodes.", "unusedConfigNodes":"You have some unused configuration nodes.",
"unusedConfigNodesLink":"Click here to see them", "unusedConfigNodesLink":"Click here to see them",
@ -201,6 +215,10 @@
"plusNMore": "+ __count__ more" "plusNMore": "+ __count__ more"
} }
}, },
"eventLog": {
"title": "Event log",
"view": "View log"
},
"diff": { "diff": {
"unresolvedCount": "__count__ unresolved conflict", "unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts", "unresolvedCount_plural": "__count__ unresolved conflicts",
@ -265,6 +283,9 @@
"settingIcon": "Icon", "settingIcon": "Icon",
"noDefaultLabel": "none", "noDefaultLabel": "none",
"defaultLabel": "use default label", "defaultLabel": "use default label",
"searchIcons": "Search icons",
"useDefault": "use default",
"description": "Description",
"errors": { "errors": {
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it" "scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it"
} }
@ -306,13 +327,11 @@
"savedNodes": "Saved nodes", "savedNodes": "Saved nodes",
"savedType": "Saved __type__", "savedType": "Saved __type__",
"saveFailed": "Save failed: __message__", "saveFailed": "Save failed: __message__",
"filename": "Filename", "filename": "Filename",
"folder": "Folder", "folder": "Folder",
"filenamePlaceholder": "file", "filenamePlaceholder": "file",
"fullFilenamePlaceholder": "a/b/file", "fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b", "folderPlaceholder": "a/b",
"breadcrumb": "Library" "breadcrumb": "Library"
}, },
"palette": { "palette": {
@ -330,6 +349,10 @@
"analysis": "analysis", "analysis": "analysis",
"advanced": "advanced" "advanced": "advanced"
}, },
"actions": {
"collapse-all": "Collapse all categories",
"expand-all": "Expand all categories"
},
"event": { "event": {
"nodeAdded": "Node added to palette:", "nodeAdded": "Node added to palette:",
"nodeAdded_plural": "Nodes added to palette", "nodeAdded_plural": "Nodes added to palette",
@ -358,7 +381,6 @@
"monthsV_plural": "__count__ months ago", "monthsV_plural": "__count__ months ago",
"yearsV": "__count__ year ago", "yearsV": "__count__ year ago",
"yearsV_plural": "__count__ years ago", "yearsV_plural": "__count__ years ago",
"yearMonthsV": "__y__ year, __count__ month ago", "yearMonthsV": "__y__ year, __count__ month ago",
"yearMonthsV_plural": "__y__ year, __count__ months ago", "yearMonthsV_plural": "__y__ year, __count__ months ago",
"yearsMonthsV": "__y__ years, __count__ month ago", "yearsMonthsV": "__y__ years, __count__ month ago",
@ -426,6 +448,7 @@
"label": "info", "label": "info",
"node": "Node", "node": "Node",
"type": "Type", "type": "Type",
"module": "Module",
"id": "ID", "id": "ID",
"status": "Status", "status": "Status",
"enabled": "Enabled", "enabled": "Enabled",
@ -434,6 +457,7 @@
"instances": "Instances", "instances": "Instances",
"properties": "Properties", "properties": "Properties",
"info": "Information", "info": "Information",
"desc": "Description",
"blank": "blank", "blank": "blank",
"null": "null", "null": "null",
"showMore": "show more", "showMore": "show more",
@ -460,8 +484,14 @@
"filtered": "__count__ hidden" "filtered": "__count__ hidden"
}, },
"context": { "context": {
"name":"Context", "name":"Context Data",
"label":"context" "label":"context",
"none": "none selected",
"refresh": "refresh to load",
"empty": "empty",
"node": "Node",
"flow": "Flow",
"global": "Global"
}, },
"palette": { "palette": {
"name": "Palette management", "name": "Palette management",
@ -477,21 +507,22 @@
"editDescription": "Edit project description", "editDescription": "Edit project description",
"editDependencies": "Edit project dependencies", "editDependencies": "Edit project dependencies",
"editReadme": "Edit README.md", "editReadme": "Edit README.md",
"showProjectSettings": "Show project settings",
"projectSettings": { "projectSettings": {
"title": "Project Settings",
"edit": "edit", "edit": "edit",
"none": "None", "none": "None",
"install": "install", "install": "install",
"removeFromProject": "remove from project", "removeFromProject": "remove from project",
"addToProject": "add to project", "addToProject": "add to project",
"none": "None",
"files": "Files", "files": "Files",
"flow": "Flow", "flow": "Flow",
"credentials": "Credentials", "credentials": "Credentials",
"invalidEncryptionKey": "Invalid encryption key", "invalidEncryptionKey": "Invalid encryption key",
"encryptionEnabled": "Encryption enabled", "encryptionEnabled": "Encryption enabled",
"encryptionDisabled": "Encryption disabled", "encryptionDisabled": "Encryption disabled",
"resetTheEncryptionKey": "Reset the encryption key:",
"setTheEncryptionKey": "Set the encryption key:", "setTheEncryptionKey": "Set the encryption key:",
"resetTheEncryptionKey": "Reset the encryption key:",
"changeTheEncryptionKey": "Change the encryption key:", "changeTheEncryptionKey": "Change the encryption key:",
"currentKey": "Current key", "currentKey": "Current key",
"newKey": "New key", "newKey": "New key",
@ -583,6 +614,7 @@
"pullUnrelatedHistory": "<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>", "pullUnrelatedHistory": "<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>",
"pullChanges": "Pull changes", "pullChanges": "Pull changes",
"history": "history", "history": "history",
"projectHistory": "Project History",
"daysAgo": "__count__ day ago", "daysAgo": "__count__ day ago",
"daysAgo_plural": "__count__ days ago", "daysAgo_plural": "__count__ days ago",
"hoursAgo": "__count__ hour ago", "hoursAgo": "__count__ hour ago",
@ -614,7 +646,9 @@
"bool": "boolean", "bool": "boolean",
"json": "JSON", "json": "JSON",
"bin": "buffer", "bin": "buffer",
"date": "timestamp" "date": "timestamp",
"jsonata": "expression",
"env": "env variable"
} }
}, },
"editableList": { "editableList": {
@ -643,6 +677,9 @@
"eval": "Error evaluating expression:\n __message__" "eval": "Error evaluating expression:\n __message__"
} }
}, },
"jsEditor": {
"title": "JavaScript editor"
},
"jsonEditor": { "jsonEditor": {
"title": "JSON editor", "title": "JSON editor",
"format": "format JSON" "format": "format JSON"
@ -705,7 +742,7 @@
"ssh-key-add": "Add an ssh key", "ssh-key-add": "Add an ssh key",
"credential-key": "Credentials encryption key", "credential-key": "Credentials encryption key",
"cant-get-ssh-key": "Error! Can't get selected SSH key path.", "cant-get-ssh-key": "Error! Can't get selected SSH key path.",
"already-exists": "already exists", "already-exists2": "already exists",
"git-error": "git error", "git-error": "git error",
"connection-failed": "Connection failed", "connection-failed": "Connection failed",
"not-git-repo": "Not a git repository", "not-git-repo": "Not a git repository",

View File

@ -189,11 +189,11 @@
"desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`.\n\nThe optional `init` parameter is used as the initial value in the aggregation." "desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`.\n\nThe optional `init` parameter is used as the initial value in the aggregation."
}, },
"$flowContext": { "$flowContext": {
"args": "string", "args": "string[, string]",
"desc": "Retrieves a flow context property.\n\nThis is a Node-RED defined function." "desc": "Retrieves a flow context property.\n\nThis is a Node-RED defined function."
}, },
"$globalContext": { "$globalContext": {
"args": "string", "args": "string[, string]",
"desc": "Retrieves a global context property.\n\nThis is a Node-RED defined function." "desc": "Retrieves a global context property.\n\nThis is a Node-RED defined function."
}, },
"$pad": { "$pad": {

View File

@ -78,6 +78,12 @@
"projects-settings": "設定" "projects-settings": "設定"
} }
}, },
"actions": {
"toggle-navigator": "ナビゲータの表示/非表示を切替",
"zoom-out": "縮小",
"zoom-reset": "拡大/縮小を初期化",
"zoom-in": "拡大"
},
"user": { "user": {
"loggedInAs": "__name__ としてログインしました", "loggedInAs": "__name__ としてログインしました",
"username": "ユーザ名", "username": "ユーザ名",
@ -97,11 +103,15 @@
"undeployedChanges": "ノードの変更をデプロイしていません", "undeployedChanges": "ノードの変更をデプロイしていません",
"nodeActionDisabled": "ノードのアクションは、サブフロー内で無効になっています", "nodeActionDisabled": "ノードのアクションは、サブフロー内で無効になっています",
"missing-types": "不明なノードが存在するため、フローを停止しました。詳細はログを確認してください。", "missing-types": "不明なノードが存在するため、フローを停止しました。詳細はログを確認してください。",
"safe-mode": "<p>セーフモードでフローを停止しました</p><p>フローを変更し、再起動するために変更をデプロイできます</p>",
"restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります", "restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります",
"credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報は暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>", "credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>",
"credentials_load_failed_reset": "<p>認証情報を復号できません</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です。</p><p>次回のデプロイでフローの認証情報ファイルがリセットされます。既存フローの認証情報は削除されます。</p>",
"missing_flow_file": "<p>プロジェクトのフローファイルが存在しません</p><p>本プロジェクトにフローファイルが登録されていません</p>", "missing_flow_file": "<p>プロジェクトのフローファイルが存在しません</p><p>本プロジェクトにフローファイルが登録されていません</p>",
"missing_package_file": "<p>プロジェクトのパッケージファイルが存在しません</p><p>本プロジェクトにはpackage.jsonファイルがありません</p>",
"project_empty": "<p>空のプロジェクトです</p><p>デフォルトのプロジェクトファイルを作成しますか?<br/>作成しない場合、エディタの外でファイルをプロジェクトへ手動で追加する必要があります</p>", "project_empty": "<p>空のプロジェクトです</p><p>デフォルトのプロジェクトファイルを作成しますか?<br/>作成しない場合、エディタの外でファイルをプロジェクトへ手動で追加する必要があります</p>",
"project_not_found": "<p>プロジェクト '__project__' が存在しません</p>" "project_not_found": "<p>プロジェクト '__project__' が存在しません</p>",
"git_merge_conflict": "<p>変更の自動マージが失敗しました</p><p>マージされていない競合を解決し、コミットしてください</p>"
}, },
"error": "<strong>エラー</strong>: __message__", "error": "<strong>エラー</strong>: __message__",
"errors": { "errors": {
@ -168,7 +178,10 @@
"modifiedFlowsDesc": "変更したノードを含むフローのみデプロイ", "modifiedFlowsDesc": "変更したノードを含むフローのみデプロイ",
"modifiedNodes": "変更したノード", "modifiedNodes": "変更したノード",
"modifiedNodesDesc": "変更したノードのみデプロイ", "modifiedNodesDesc": "変更したノードのみデプロイ",
"restartFlows": "フローを再起動",
"restartFlowsDesc": "デプロイされた現在のフローを再起動",
"successfulDeploy": "デプロイが成功しました", "successfulDeploy": "デプロイが成功しました",
"successfulRestart": "フローの再起動が成功しました",
"deployFailed": "デプロイが失敗しました: __message__", "deployFailed": "デプロイが失敗しました: __message__",
"unusedConfigNodes": "使われていない「ノードの設定」があります。", "unusedConfigNodes": "使われていない「ノードの設定」があります。",
"unusedConfigNodesLink": "設定を参照する", "unusedConfigNodesLink": "設定を参照する",
@ -236,6 +249,7 @@
"output": "出力:", "output": "出力:",
"deleteSubflow": "サブフローを削除", "deleteSubflow": "サブフローを削除",
"info": "詳細", "info": "詳細",
"category": "カテゴリ",
"format": "マークダウン形式", "format": "マークダウン形式",
"errors": { "errors": {
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません", "noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
@ -260,6 +274,9 @@
"settingIcon": "アイコン", "settingIcon": "アイコン",
"noDefaultLabel": "なし", "noDefaultLabel": "なし",
"defaultLabel": "既定の名前を使用", "defaultLabel": "既定の名前を使用",
"searchIcons": "アイコンを検索",
"useDefault": "デフォルトを使用",
"description": "詳細",
"errors": { "errors": {
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします" "scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします"
} }
@ -312,6 +329,7 @@
"noInfo": "情報がありません", "noInfo": "情報がありません",
"filter": "ノードを検索", "filter": "ノードを検索",
"search": "ノードを検索", "search": "ノードを検索",
"addCategory": "新規追加...",
"label": { "label": {
"subflows": "サブフロー", "subflows": "サブフロー",
"input": "入力", "input": "入力",
@ -322,6 +340,10 @@
"analysis": "分析", "analysis": "分析",
"advanced": "その他" "advanced": "その他"
}, },
"actions": {
"collapse-all": "全カテゴリを折畳む",
"expand-all": "全カテゴリを展開"
},
"event": { "event": {
"nodeAdded": "ノードをパレットへ追加しました:", "nodeAdded": "ノードをパレットへ追加しました:",
"nodeAdded_plural": "ノードをパレットへ追加しました", "nodeAdded_plural": "ノードをパレットへ追加しました",
@ -424,6 +446,7 @@
"instances": "インスタンス", "instances": "インスタンス",
"properties": "プロパティ", "properties": "プロパティ",
"info": "情報", "info": "情報",
"desc": "詳細",
"blank": "ブランク", "blank": "ブランク",
"null": "ヌル", "null": "ヌル",
"showMore": "さらに表示", "showMore": "さらに表示",
@ -449,6 +472,16 @@
"filterAll": "全て", "filterAll": "全て",
"filtered": "__count__ 個が無効" "filtered": "__count__ 個が無効"
}, },
"context": {
"name": "コンテキストデータ",
"label": "コンテキストデータ",
"none": "選択されていません",
"refresh": "読み込みのため更新してください",
"empty": "データが存在しません",
"node": "Node",
"flow": "Flow",
"global": "Global"
},
"palette": { "palette": {
"name": "パレットの管理", "name": "パレットの管理",
"label": "パレット" "label": "パレット"
@ -463,7 +496,9 @@
"editDescription": "プロジェクトの詳細を編集", "editDescription": "プロジェクトの詳細を編集",
"editDependencies": "プロジェクトの依存関係を編集", "editDependencies": "プロジェクトの依存関係を編集",
"editReadme": "README.mdを編集", "editReadme": "README.mdを編集",
"showProjectSettings": "プロジェクト設定を表示",
"projectSettings": { "projectSettings": {
"title": "プロジェクト設定",
"edit": "編集", "edit": "編集",
"none": "なし", "none": "なし",
"install": "インストール", "install": "インストール",
@ -568,7 +603,7 @@
"pullUnrelatedHistory": "<p>リモートに関連のないコミット履歴があります。</p><p>本当に変更をプルしてローカルリポジトリに反映しますか?</p>", "pullUnrelatedHistory": "<p>リモートに関連のないコミット履歴があります。</p><p>本当に変更をプルしてローカルリポジトリに反映しますか?</p>",
"pullChanges": "プル変更", "pullChanges": "プル変更",
"history": "履歴", "history": "履歴",
"plural": "", "projectHistory": "プロジェクト履歴",
"daysAgo": "__count__ 日前", "daysAgo": "__count__ 日前",
"daysAgo_plural": "__count__ 日前", "daysAgo_plural": "__count__ 日前",
"hoursAgo": "__count__ 時間前", "hoursAgo": "__count__ 時間前",
@ -600,7 +635,9 @@
"bool": "真偽", "bool": "真偽",
"json": "JSON", "json": "JSON",
"bin": "バッファ", "bin": "バッファ",
"date": "日時" "date": "日時",
"jsonata": "JSONata式",
"env": "環境変数"
} }
}, },
"editableList": { "editableList": {
@ -629,6 +666,9 @@
"eval": "表現評価エラー:\n __message__" "eval": "表現評価エラー:\n __message__"
} }
}, },
"jsEditor": {
"title": "JavaScriptエディタ"
},
"jsonEditor": { "jsonEditor": {
"title": "JSONエディタ", "title": "JSONエディタ",
"format": "JSONフォーマット" "format": "JSONフォーマット"
@ -691,14 +731,14 @@
"ssh-key-add": "SSHキーの追加", "ssh-key-add": "SSHキーの追加",
"credential-key": "認証情報の暗号化キー", "credential-key": "認証情報の暗号化キー",
"cant-get-ssh-key": "エラー! 選択したSSHキーのパスを取得できません。", "cant-get-ssh-key": "エラー! 選択したSSHキーのパスを取得できません。",
"already-exists": "既に存在します", "already-exists2": "既に存在します",
"git-error": "Gitエラー", "git-error": "Gitエラー",
"connection-failed": "接続に失敗しました", "connection-failed": "接続に失敗しました",
"not-git-repo": "Gitリポジトリではありません", "not-git-repo": "Gitリポジトリではありません",
"repo-not-found": "リポジトリが見つかりません" "repo-not-found": "リポジトリが見つかりません"
}, },
"default-files": { "default-files": {
"create": "プロジェクト関連ファイルの作成", "create": "プロジェクト関連ファイルの作成",
"desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。", "desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。",
"desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。", "desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。",
"desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。", "desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。",
@ -714,7 +754,7 @@
"desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。", "desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。",
"desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。", "desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。",
"desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。", "desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。",
"desc7": "キーはプロジェクトファイルと別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。", "desc7": "キーはプロジェクトファイルと別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。",
"credentials": "認証情報", "credentials": "認証情報",
"enable": "暗号化を有効にする", "enable": "暗号化を有効にする",
"disable": "暗号化を無効にする", "disable": "暗号化を無効にする",
@ -799,7 +839,7 @@
"no-empty": "デフォルトのファイル群を空でないプロジェクトに作成することはできません。", "no-empty": "デフォルトのファイル群を空でないプロジェクトに作成することはできません。",
"git-error": "Gitエラー" "git-error": "Gitエラー"
}, },
"errors" : { "errors": {
"no-username-email": "Gitクライアントのユーザ名/emailが設定されていません。", "no-username-email": "Gitクライアントのユーザ名/emailが設定されていません。",
"unexpected": "予期しないエラーが発生しました", "unexpected": "予期しないエラーが発生しました",
"code": "コード" "code": "コード"

View File

@ -214,5 +214,9 @@
"$toMillis": { "$toMillis": {
"args": "timestamp", "args": "timestamp",
"desc": "ISO 8601形式の文字列 `timestamp` を、Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値へ変換します。 文字列が正しい形式でない場合、エラーとなります。" "desc": "ISO 8601形式の文字列 `timestamp` を、Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値へ変換します。 文字列が正しい形式でない場合、エラーとなります。"
},
"$env": {
"args": "arg",
"desc": "環境変数の値を返します。\n\n本関数はNode-REDの定義関数です。"
} }
} }

View File

@ -0,0 +1,14 @@
{
"name": "@node-red/editor-client",
"version": "0.20.0-alpha.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/node-red/node-red.git"
},
"contributors": [
{ "name": "Nick O'Leary" },
{ "name": "Dave Conway-Jones"}
],
"main": "./lib/index.js"
}

View File

@ -0,0 +1,50 @@
How to build the custom ACE modes for Node-RED
----------------------------------------------
Node-RED includes custom JSONata and JavaScript modes.
## JSONata
The `ace/mode/jsonata` mode is maintained under `editor-client/src/vendor/jsonata`.
Those files are edited in place and copied into the build by Grunt.
## JavaScript
The `ace/mode/nrjavascript` mode is used exclusively by the Function node. It
inherits almost entirely from the normal JavaScript mode. The one key difference
is that it wraps the code with a Function before parsing. This is required to
avoid some false-flagged errors.
The source of the mode is under `editor-client/src/ace/mode`. If those files are
modified in anyway, they *must* be manually built to generate the files under
`editor-client/src/ace/bin` and checked in. Those files are the ones the Grunt
built copies out in the Node-RED build.
### Building the mode files
#### Setup build environment
cd /tmp/
git clone https://github.com/ajaxorg/ace.git
cd ace
npm install
#### Copy mode src files into build environment
cd <node-red-source-directory
cp packages/node_modules/@node-red/editor-client/src/ace/mode/* \
/tmp/ace/lib/ace/mode/
#### Run the build
cd /tmp/ace
node ./Makefile.dryice.js -m -nc
#### Copy the built versions back
cp build/src-min-noconflict/*-nrjavascript.js \
<node-red-source-directory>/packages/node_modules/@node-red/editor-client/src/ace/bin/
cp build/src-min-noconflict/snippets/nrjavascript.js \
<node-red-source-directory>/packages/node_modules/@node-red/editor-client/src/ace/bin/snippets/

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="nrjavascript"});
(function() {
ace.require(["ace/snippets/nrjavascript"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,49 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var JavaScriptMode = require("./javascript").Mode;
var WorkerClient = require("../worker/worker_client").WorkerClient;
var Mode = function() {
// Inherit everything from the standard JavaScript mode
JavaScriptMode.call(this);
};
oop.inherits(Mode, JavaScriptMode);
(function() {
// Insert our custom worker
this.createWorker = function(session) {
var worker = new WorkerClient(["ace"], "ace/mode/nrjavascript_worker", "NRJavaScriptWorker");
worker.attachToDocument(session.getDocument());
worker.on("annotate", function(results) {
session.setAnnotations(results.data);
});
worker.on("terminate", function() {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/nrjavascript";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@ -0,0 +1,189 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var Mirror = require("../worker/mirror").Mirror;
var lint = require("./javascript/jshint").JSHINT;
function startRegex(arr) {
return RegExp("^(" + arr.join("|") + ")");
}
var disabledWarningsRe = startRegex([
"Bad for in variable '(.+)'.",
'Missing "use strict"'
]);
var errorsRe = startRegex([
"Unexpected",
"Expected ",
"Confusing (plus|minus)",
"\\{a\\} unterminated regular expression",
"Unclosed ",
"Unmatched ",
"Unbegun comment",
"Bad invocation",
"Missing space after",
"Missing operator at"
]);
var infoRe = startRegex([
"Expected an assignment",
"Bad escapement of EOL",
"Unexpected comma",
"Unexpected space",
"Missing radix parameter.",
"A leading decimal point can",
"\\['{a}'\\] is better written in dot notation.",
"'{a}' used out of scope"
]);
var NRJavaScriptWorker = exports.NRJavaScriptWorker = function(sender) {
Mirror.call(this, sender);
this.setTimeout(500);
this.setOptions();
};
oop.inherits(NRJavaScriptWorker, Mirror);
(function() {
this.setOptions = function(options) {
this.options = options || {
// undef: true,
// unused: true,
esnext: true,
moz: true,
devel: true,
browser: true,
node: true,
laxcomma: true,
laxbreak: true,
lastsemic: true,
onevar: false,
passfail: false,
maxerr: 100,
expr: true,
multistr: true,
globalstrict: true
};
this.doc.getValue() && this.deferredUpdate.schedule(100);
};
this.changeOptions = function(newOptions) {
oop.mixin(this.options, newOptions);
this.doc.getValue() && this.deferredUpdate.schedule(100);
};
this.isValidJS = function(str) {
try {
// evaluated code can only create variables in this function
eval("throw 0;" + str);
} catch(e) {
if (e === 0)
return true;
}
return false;
};
this.onUpdate = function() {
var value = this.doc.getValue();
value = value.replace(/^#!.*\n/, "\n");
if (!value)
return this.sender.emit("annotate", []);
// [Node-RED] wrap the code in a function
value = "async function __nodered__(msg) {\n"+value+"\n}";
var errors = [];
// jshint reports many false errors
// report them as error only if code is actually invalid
var maxErrorLevel = this.isValidJS(value) ? "warning" : "error";
// var start = new Date();
lint(value, this.options, this.options.globals);
var results = lint.errors;
var errorAdded = false;
for (var i = 0; i < results.length; i++) {
var error = results[i];
if (!error)
continue;
var raw = error.raw;
var type = "warning";
if (raw == "Missing semicolon.") {
var str = error.evidence.substr(error.character);
str = str.charAt(str.search(/\S/));
if (maxErrorLevel == "error" && str && /[\w\d{(['"]/.test(str)) {
error.reason = 'Missing ";" before statement';
type = "error";
} else {
type = "info";
}
}
else if (disabledWarningsRe.test(raw)) {
continue;
}
else if (infoRe.test(raw)) {
type = "info";
}
else if (errorsRe.test(raw)) {
errorAdded = true;
type = maxErrorLevel;
}
else if (raw == "'{a}' is not defined.") {
type = "warning";
}
else if (raw == "'{a}' is defined but never used.") {
type = "info";
}
errors.push({
// [Node-RED] offset the row for the added line
row: error.line-2,
column: error.character-1,
text: error.reason,
type: type,
raw: raw
});
if (errorAdded) {
// break;
}
}
// console.log("lint time: " + (new Date() - start));
this.sender.emit("annotate", errors);
};
}).call(NRJavaScriptWorker.prototype);
});

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 291 B

After

Width:  |  Height:  |  Size: 291 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

View File

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 368 B

View File

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 290 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

View File

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View File

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1019 B

After

Width:  |  Height:  |  Size: 1019 B

View File

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 600 B

View File

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 410 B

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