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_2225

This commit is contained in:
Nick O'Leary 2019-08-12 15:05:12 +01:00
commit 607bc42f59
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
78 changed files with 2886 additions and 2640 deletions

View File

@ -28,7 +28,7 @@ To help us understand the issue, please fill-in as much of the following informa
### Please tell us about your environment: ### Please tell us about your environment:
- [ ] Node-RED version: - [ ] Node-RED version:
- [ ] node.js version: - [ ] Node.js version:
- [ ] npm version: - [ ] npm version:
- [ ] Platform/OS: - [ ] Platform/OS:
- [ ] Browser: - [ ] Browser:

View File

@ -33,7 +33,7 @@ To help us understand the issue, please fill-in as much of the following informa
### Please tell us about your environment: ### Please tell us about your environment:
- [ ] Node-RED version: - [ ] Node-RED version:
- [ ] node.js version: - [ ] Node.js version:
- [ ] npm version: - [ ] npm version:
- [ ] Platform/OS: - [ ] Platform/OS:
- [ ] Browser: - [ ] Browser:

View File

@ -9,5 +9,3 @@ matrix:
before_script: before_script:
- npm install -g istanbul coveralls - npm install -g istanbul coveralls
- node_js: "8" - node_js: "8"
allow_failures:
- node_js: "12"

View File

@ -725,7 +725,7 @@ Nodes
- Initial support of sequence rules for SWITCH node (#1545) - Initial support of sequence rules for SWITCH node (#1545)
- initial support of SORT node (#1500) - initial support of SORT node (#1500)
- Inject node - let once delay be editable (#1541) - Inject node - let once delay be editable (#1541)
- Introduce `nodeMaxMessageBufferLength` setting for msg sequence nodes - Introduce `nodeMessageBufferMaxLength` setting for msg sequence nodes
- Let CSV correct parts if we remove header row. - Let CSV correct parts if we remove header row.
- let default apply if msg.delay not set in override mode. (#1397) - let default apply if msg.delay not set in override mode. (#1397)
- let trigger node be reset by boolean message (#1554) - let trigger node be reset by boolean message (#1554)

View File

@ -26,7 +26,7 @@ relevant nodes, press Ctrl-E and copy the flow data from the Export dialog.
At a minimum, please include: At a minimum, please include:
- Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly. - Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly.
- Version of node.js - what does `node -v` say? - Version of Node.js - what does `node -v` say?
## Feature requests ## Feature requests

View File

@ -496,7 +496,9 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-chmod'); grunt.loadNpmTasks('grunt-chmod');
grunt.loadNpmTasks('grunt-jsonlint'); grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-mocha-istanbul'); grunt.loadNpmTasks('grunt-mocha-istanbul');
grunt.loadNpmTasks('grunt-webdriver'); if (fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
grunt.loadNpmTasks('grunt-webdriver');
}
grunt.loadNpmTasks('grunt-jsdoc'); grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-jsdoc-to-markdown'); grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
grunt.loadNpmTasks('grunt-npm-command'); grunt.loadNpmTasks('grunt-npm-command');
@ -555,8 +557,8 @@ module.exports = function(grunt) {
}); });
grunt.registerTask('verifyUiTestDependencies', function() { grunt.registerTask('verifyUiTestDependencies', function() {
if (!fs.existsSync(path.join("node_modules", "chromedriver"))) { if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
grunt.fail.fatal('You need to run "npm install chromedriver@2" before running UI test.'); grunt.fail.fatal('You need to install the UI test dependencies first.\nUse the script in "scripts/install-ui-test-dependencies.sh"');
return false; return false;
} }
}); });
@ -579,9 +581,15 @@ module.exports = function(grunt) {
'Runs code style check on editor code', 'Runs code style check on editor code',
['jshint:editor']); ['jshint:editor']);
grunt.registerTask('test-ui', if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
'Builds editor content then runs unit tests on editor ui', grunt.registerTask('test-ui',
['verifyUiTestDependencies','build','jshint:editor','webdriver:all']); 'Builds editor content then runs unit tests on editor ui',
['verifyUiTestDependencies']);
} else {
grunt.registerTask('test-ui',
'Builds editor content then runs unit tests on editor ui',
['verifyUiTestDependencies','build','jshint:editor','webdriver:all']);
}
grunt.registerTask('test-nodes', grunt.registerTask('test-nodes',
'Runs unit tests on core nodes', 'Runs unit tests on core nodes',

View File

@ -54,12 +54,9 @@
"mqtt": "2.18.8", "mqtt": "2.18.8",
"multer": "1.4.1", "multer": "1.4.1",
"mustache": "3.0.1", "mustache": "3.0.1",
"node-red-node-email": "^1.6.2",
"node-red-node-feedparser": "^0.1.14",
"node-red-node-rbe": "^0.2.4", "node-red-node-rbe": "^0.2.4",
"node-red-node-sentiment": "^0.1.3", "node-red-node-sentiment": "^0.1.3",
"node-red-node-tail": "^0.0.2", "node-red-node-tail": "^0.0.2",
"node-red-node-twitter": "^1.1.5",
"nopt": "4.0.1", "nopt": "4.0.1",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
"on-headers": "1.0.2", "on-headers": "1.0.2",
@ -98,7 +95,6 @@
"grunt-npm-command": "~0.1.2", "grunt-npm-command": "~0.1.2",
"grunt-sass": "~2.0.0", "grunt-sass": "~2.0.0",
"grunt-simple-mocha": "~0.4.1", "grunt-simple-mocha": "~0.4.1",
"grunt-webdriver": "^2.0.3",
"http-proxy": "^1.16.2", "http-proxy": "^1.16.2",
"istanbul": "0.4.5", "istanbul": "0.4.5",
"minami": "1.2.3", "minami": "1.2.3",
@ -108,11 +104,7 @@
"sinon": "1.17.7", "sinon": "1.17.7",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",
"supertest": "3.4.2", "supertest": "3.4.2",
"wdio-chromedriver-service": "^0.1.5", "node-red-node-test-helper": "^0.2.3",
"wdio-mocha-framework": "^0.6.4",
"wdio-spec-reporter": "^0.1.5",
"webdriverio": "^4.14.1",
"node-red-node-test-helper": "^0.2.2",
"jsdoc-nr-template": "node-red/jsdoc-nr-template" "jsdoc-nr-template": "node-red/jsdoc-nr-template"
}, },
"engines": { "engines": {

View File

@ -30,7 +30,8 @@ module.exports = {
scope: req.params.scope, scope: req.params.scope,
id: req.params.id, id: req.params.id,
key: req.params[0], key: req.params[0],
store: req.query['store'] store: req.query['store'],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.context.getValue(opts).then(function(result) { runtimeAPI.context.getValue(opts).then(function(result) {
res.json(result); res.json(result);
@ -45,7 +46,8 @@ module.exports = {
scope: req.params.scope, scope: req.params.scope,
id: req.params.id, id: req.params.id,
key: req.params[0], key: req.params[0],
store: req.query['store'] store: req.query['store'],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.context.delete(opts).then(function(result) { runtimeAPI.context.delete(opts).then(function(result) {
res.status(204).end(); res.status(204).end();

View File

@ -24,7 +24,8 @@ module.exports = {
get: function(req,res) { get: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.flows.getFlow(opts).then(function(result) { runtimeAPI.flows.getFlow(opts).then(function(result) {
return res.json(result); return res.json(result);
@ -35,7 +36,8 @@ module.exports = {
post: function(req,res) { post: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
flow: req.body flow: req.body,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.flows.addFlow(opts).then(function(id) { runtimeAPI.flows.addFlow(opts).then(function(id) {
return res.json({id:id}); return res.json({id:id});
@ -47,7 +49,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
flow: req.body flow: req.body,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.flows.updateFlow(opts).then(function(id) { runtimeAPI.flows.updateFlow(opts).then(function(id) {
return res.json({id:id}); return res.json({id:id});
@ -58,7 +61,8 @@ module.exports = {
delete: function(req,res) { delete: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.flows.deleteFlow(opts).then(function() { runtimeAPI.flows.deleteFlow(opts).then(function() {
res.status(204).end(); res.status(204).end();

View File

@ -27,7 +27,8 @@ module.exports = {
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"}); return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
} }
var opts = { var opts = {
user: req.user user: req.user,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.flows.getFlows(opts).then(function(result) { runtimeAPI.flows.getFlows(opts).then(function(result) {
if (version === "v1") { if (version === "v1") {
@ -46,7 +47,8 @@ module.exports = {
} }
var opts = { var opts = {
user: req.user, user: req.user,
deploymentType: req.get("Node-RED-Deployment-Type")||"full" deploymentType: req.get("Node-RED-Deployment-Type")||"full",
req: apiUtils.getRequestLogObject(req)
} }
if (opts.deploymentType !== 'reload') { if (opts.deploymentType !== 'reload') {

View File

@ -48,13 +48,13 @@ 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.getModuleCatalogs,apiUtil.errorHandler);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,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);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler); adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler); adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
// Context // Context
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler); adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);

View File

@ -24,7 +24,8 @@ module.exports = {
}, },
getAll: function(req,res) { getAll: function(req,res) {
var opts = { var opts = {
user: req.user user: req.user,
req: apiUtils.getRequestLogObject(req)
} }
if (req.get("accept") == "application/json") { if (req.get("accept") == "application/json") {
runtimeAPI.nodes.getNodeList(opts).then(function(list) { runtimeAPI.nodes.getNodeList(opts).then(function(list) {
@ -42,7 +43,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
module: req.body.module, module: req.body.module,
version: req.body.version version: req.body.version,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.addModule(opts).then(function(info) { runtimeAPI.nodes.addModule(opts).then(function(info) {
res.json(info); res.json(info);
@ -54,7 +56,8 @@ module.exports = {
delete: function(req,res) { delete: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
module: req.params[0] module: req.params[0],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.removeModule(opts).then(function() { runtimeAPI.nodes.removeModule(opts).then(function() {
res.status(204).end(); res.status(204).end();
@ -66,7 +69,8 @@ module.exports = {
getSet: function(req,res) { getSet: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params[0] + "/" + req.params[2] id: req.params[0] + "/" + req.params[2],
req: apiUtils.getRequestLogObject(req)
} }
if (req.get("accept") === "application/json") { if (req.get("accept") === "application/json") {
runtimeAPI.nodes.getNodeInfo(opts).then(function(result) { runtimeAPI.nodes.getNodeInfo(opts).then(function(result) {
@ -87,7 +91,8 @@ module.exports = {
getModule: function(req,res) { getModule: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
module: req.params[0] module: req.params[0],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.getModuleInfo(opts).then(function(result) { runtimeAPI.nodes.getModuleInfo(opts).then(function(result) {
res.send(result); res.send(result);
@ -106,7 +111,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params[0] + "/" + req.params[2], id: req.params[0] + "/" + req.params[2],
enabled: body.enabled enabled: body.enabled,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.setNodeSetState(opts).then(function(result) { runtimeAPI.nodes.setNodeSetState(opts).then(function(result) {
res.send(result); res.send(result);
@ -125,7 +131,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
module: req.params[0], module: req.params[0],
enabled: body.enabled enabled: body.enabled,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.setModuleState(opts).then(function(result) { runtimeAPI.nodes.setModuleState(opts).then(function(result) {
res.send(result); res.send(result);
@ -139,7 +146,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
module: req.params[0], module: req.params[0],
lang: req.query.lng lang: req.query.lng,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) { runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
res.json(result); res.json(result);
@ -152,7 +160,8 @@ module.exports = {
getModuleCatalogs: function(req,res) { getModuleCatalogs: function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
lang: req.query.lng lang: req.query.lng,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) { runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
res.json(result); res.json(result);
@ -164,7 +173,8 @@ module.exports = {
getIcons: function(req,res) { getIcons: function(req,res) {
var opts = { var opts = {
user: req.user user: req.user,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.getIconList(opts).then(function(list) { runtimeAPI.nodes.getIconList(opts).then(function(list) {
res.json(list); res.json(list);

View File

@ -22,7 +22,8 @@ var needsPermission = require("../auth").needsPermission;
function listProjects(req,res) { function listProjects(req,res) {
var opts = { var opts = {
user: req.user user: req.user,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.listProjects(opts).then(function(result) { runtimeAPI.projects.listProjects(opts).then(function(result) {
res.json(result); res.json(result);
@ -33,7 +34,8 @@ function listProjects(req,res) {
function getProject(req,res) { function getProject(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getProject(opts).then(function(data) { runtimeAPI.projects.getProject(opts).then(function(data) {
if (data) { if (data) {
@ -49,7 +51,8 @@ function getProjectStatus(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: req.query.remote remote: req.query.remote,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getStatus(opts).then(function(data){ runtimeAPI.projects.getStatus(opts).then(function(data){
if (data) { if (data) {
@ -64,7 +67,8 @@ function getProjectStatus(req,res) {
function getProjectRemotes(req,res) { function getProjectRemotes(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getRemotes(opts).then(function(data) { runtimeAPI.projects.getRemotes(opts).then(function(data) {
res.json(data); res.json(data);
@ -98,7 +102,8 @@ module.exports = {
app.post("/", needsPermission("projects.write"), function(req,res) { app.post("/", needsPermission("projects.write"), function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
project: req.body project: req.body,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.createProject(opts).then(function(result) { runtimeAPI.projects.createProject(opts).then(function(result) {
res.json(result); res.json(result);
@ -112,7 +117,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
project: req.body project: req.body,
req: apiUtils.getRequestLogObject(req)
} }
if (req.body.active) { if (req.body.active) {
@ -150,7 +156,8 @@ module.exports = {
app.delete("/:id", needsPermission("projects.write"), function(req,res) { app.delete("/:id", needsPermission("projects.write"), function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.deleteProject(opts).then(function() { runtimeAPI.projects.deleteProject(opts).then(function() {
res.status(204).end(); res.status(204).end();
@ -168,7 +175,8 @@ module.exports = {
app.get("/:id/files", needsPermission("projects.read"), function(req,res) { app.get("/:id/files", needsPermission("projects.read"), function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getFiles(opts).then(function(data) { runtimeAPI.projects.getFiles(opts).then(function(data) {
res.json(data); res.json(data);
@ -185,7 +193,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.params[0], path: req.params[0],
tree: req.params.treeish tree: req.params.treeish,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getFile(opts).then(function(data) { runtimeAPI.projects.getFile(opts).then(function(data) {
res.json({content:data}); res.json({content:data});
@ -199,7 +208,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.params[0] path: req.params[0],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.revertFile(opts).then(function() { runtimeAPI.projects.revertFile(opts).then(function() {
@ -214,7 +224,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.params[0] path: req.params[0],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.stageFile(opts).then(function() { runtimeAPI.projects.stageFile(opts).then(function() {
getProjectStatus(req,res); getProjectStatus(req,res);
@ -228,7 +239,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.body.files path: req.body.files,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.stageFile(opts).then(function() { runtimeAPI.projects.stageFile(opts).then(function() {
getProjectStatus(req,res); getProjectStatus(req,res);
@ -242,7 +254,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
message: req.body.message message: req.body.message,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.commit(opts).then(function() { runtimeAPI.projects.commit(opts).then(function() {
getProjectStatus(req,res); getProjectStatus(req,res);
@ -256,7 +269,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.params[0] path: req.params[0],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.unstageFile(opts).then(function() { runtimeAPI.projects.unstageFile(opts).then(function() {
getProjectStatus(req,res); getProjectStatus(req,res);
@ -269,7 +283,8 @@ module.exports = {
app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) { app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.unstageFile(opts).then(function() { runtimeAPI.projects.unstageFile(opts).then(function() {
getProjectStatus(req,res); getProjectStatus(req,res);
@ -284,7 +299,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.params[0], path: req.params[0],
type: req.params.type type: req.params.type,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getFileDiff(opts).then(function(data) { runtimeAPI.projects.getFileDiff(opts).then(function(data) {
res.json({ res.json({
@ -301,7 +317,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
limit: req.query.limit || 20, limit: req.query.limit || 20,
before: req.query.before before: req.query.before,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getCommits(opts).then(function(data) { runtimeAPI.projects.getCommits(opts).then(function(data) {
res.json(data); res.json(data);
@ -315,7 +332,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
sha: req.params.sha sha: req.params.sha,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getCommit(opts).then(function(data) { runtimeAPI.projects.getCommit(opts).then(function(data) {
res.json({commit:data}); res.json({commit:data});
@ -330,7 +348,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: req.params[0], remote: req.params[0],
track: req.query.u track: req.query.u,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.push(opts).then(function(data) { runtimeAPI.projects.push(opts).then(function(data) {
res.status(204).end(); res.status(204).end();
@ -346,7 +365,8 @@ module.exports = {
id: req.params.id, id: req.params.id,
remote: req.params[0], remote: req.params[0],
track: req.query.setUpstream, track: req.query.setUpstream,
allowUnrelatedHistories: req.query.allowUnrelatedHistories allowUnrelatedHistories: req.query.allowUnrelatedHistories,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.pull(opts).then(function(data) { runtimeAPI.projects.pull(opts).then(function(data) {
res.status(204).end(); res.status(204).end();
@ -359,7 +379,8 @@ module.exports = {
app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) { app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.abortMerge(opts).then(function() { runtimeAPI.projects.abortMerge(opts).then(function() {
res.status(204).end(); res.status(204).end();
@ -374,7 +395,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
path: req.params[0], path: req.params[0],
resolution: req.body.resolutions resolution: req.body.resolutions,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.resolveMerge(opts).then(function() { runtimeAPI.projects.resolveMerge(opts).then(function() {
res.status(204).end(); res.status(204).end();
@ -388,7 +410,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: false remote: false,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getBranches(opts).then(function(data) { runtimeAPI.projects.getBranches(opts).then(function(data) {
res.json(data); res.json(data);
@ -403,7 +426,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
branch: req.params.branchName, branch: req.params.branchName,
force: !!req.query.force force: !!req.query.force,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.deleteBranch(opts).then(function(data) { runtimeAPI.projects.deleteBranch(opts).then(function(data) {
res.status(204).end(); res.status(204).end();
@ -417,7 +441,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: true remote: true,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getBranches(opts).then(function(data) { runtimeAPI.projects.getBranches(opts).then(function(data) {
res.json(data); res.json(data);
@ -431,7 +456,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
branch: req.params[0] branch: req.params[0],
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.getBranchStatus(opts).then(function(data) { runtimeAPI.projects.getBranchStatus(opts).then(function(data) {
res.json(data); res.json(data);
@ -446,7 +472,8 @@ module.exports = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
branch: req.body.name, branch: req.body.name,
create: req.body.create create: req.body.create,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.setBranch(opts).then(function(data) { runtimeAPI.projects.setBranch(opts).then(function(data) {
res.json(data); res.json(data);
@ -463,7 +490,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: req.body remote: req.body,
req: apiUtils.getRequestLogObject(req)
} }
if (/^https?:\/\/[^/]+@/i.test(req.body.url)) { if (/^https?:\/\/[^/]+@/i.test(req.body.url)) {
res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"}); res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"});
@ -481,7 +509,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: req.params.remoteName remote: req.params.remoteName,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.removeRemote(opts).then(function(data) { runtimeAPI.projects.removeRemote(opts).then(function(data) {
getProjectRemotes(req,res); getProjectRemotes(req,res);
@ -497,7 +526,8 @@ module.exports = {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id, id: req.params.id,
remote: remote remote: remote,
req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.projects.updateRemote(opts).then(function() { runtimeAPI.projects.updateRemote(opts).then(function() {
res.status(204).end(); res.status(204).end();

View File

@ -47,5 +47,12 @@ module.exports = {
code: err.code||"unexpected_error", code: err.code||"unexpected_error",
message: err.message||err.toString() message: err.message||err.toString()
}); });
},
getRequestLogObject: function(req) {
return {
user: req.user,
path: req.path,
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
}
} }
} }

View File

@ -322,6 +322,31 @@
"description": "Description", "description": "Description",
"show": "Show", "show": "Show",
"hide": "Hide", "hide": "Hide",
"locale": "Select UI Language",
"icon": "Icon",
"inputType": "Input type",
"previewUI": "Preview UI",
"previewOK": "Preview OK",
"types": {
"str": "string",
"num": "number",
"bool": "bool",
"json": "json",
"bin": "buffer",
"env": "env var",
"no-value": "no value"
},
"menu": {
"input": "input",
"select": "select",
"checkbox": "checkbox",
"spinner": "spinner",
"hidden": "label only"
},
"spinner": {
"min": "min",
"max": "max"
},
"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",
"invalidProperties": "Invalid properties:" "invalidProperties": "Invalid properties:"
@ -940,9 +965,11 @@
}, },
"editor-tab": { "editor-tab": {
"properties": "Properties", "properties": "Properties",
"envProperties": "Environment Variables",
"description": "Description", "description": "Description",
"appearance": "Appearance", "appearance": "Appearance",
"env": "Environment Variables" "preview": "UI Preview",
"defaultValue": "Default value"
}, },
"languages" : { "languages" : {
"de": "German", "de": "German",

View File

@ -80,7 +80,7 @@
"projects-new": "新規", "projects-new": "新規",
"projects-open": "開く", "projects-open": "開く",
"projects-settings": "設定", "projects-settings": "設定",
"showNodeLabelDefault": "追加したノードのラベルを表示する" "showNodeLabelDefault": "追加したノードのラベルを表示"
} }
}, },
"actions": { "actions": {
@ -322,6 +322,31 @@
"description": "詳細", "description": "詳細",
"show": "表示", "show": "表示",
"hide": "非表示", "hide": "非表示",
"locale": "UI言語の選択",
"icon": "記号",
"inputType": "入力形式",
"previewUI": "UI確認",
"previewOK": "確認OK",
"types": {
"str": "文字列",
"num": "数値",
"bool": "真偽",
"json": "JSON",
"bin": "バッファ",
"env": "環境変数",
"no-value": "値無し"
},
"menu": {
"input": "入力",
"select": "選択",
"checkbox": "チェックボックス",
"spinner": "数値",
"hidden": "ラベルのみ"
},
"spinner": {
"min": "最小",
"max": "最大"
},
"errors": { "errors": {
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします", "scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします",
"invalidProperties": "プロパティが不正です:" "invalidProperties": "プロパティが不正です:"
@ -352,7 +377,8 @@
"pasteNode": "ノードを貼り付け", "pasteNode": "ノードを貼り付け",
"undoChange": "変更操作を戻す", "undoChange": "変更操作を戻す",
"searchBox": "ノードを検索", "searchBox": "ノードを検索",
"managePalette": "パレットの管理" "managePalette": "パレットの管理",
"actionList": "動作一覧"
}, },
"library": { "library": {
"library": "ライブラリ", "library": "ライブラリ",
@ -528,11 +554,13 @@
"none": "選択されていません", "none": "選択されていません",
"refresh": "読み込みのため更新してください", "refresh": "読み込みのため更新してください",
"empty": "データが存在しません", "empty": "データが存在しません",
"node": "Node", "node": "ノード",
"flow": "Flow", "flow": "フロー",
"global": "Global", "global": "グローバル",
"deleteConfirm": "データを削除しても良いですか?", "deleteConfirm": "データを削除しても良いですか?",
"autoRefresh": "自動更新" "autoRefresh": "自動更新",
"refrsh": "更新",
"delete": "削除"
}, },
"palette": { "palette": {
"name": "パレットの管理", "name": "パレットの管理",
@ -737,7 +765,16 @@
}, },
"jsonEditor": { "jsonEditor": {
"title": "JSONエディタ", "title": "JSONエディタ",
"format": "JSONフォーマット" "format": "JSONフォーマット",
"rawMode": "JSONを編集",
"uiMode": "ビジュアルエディタ",
"insertAbove": "上に挿入",
"insertBelow": "下に挿入",
"addItem": "要素を追加",
"copyPath": "要素のパスをコピー",
"expandItems": "要素を展開",
"collapseItems": "要素を折り畳む",
"duplicate": "複製"
}, },
"markdownEditor": { "markdownEditor": {
"title": "マークダウンエディタ", "title": "マークダウンエディタ",
@ -929,9 +966,9 @@
"properties": "プロパティ", "properties": "プロパティ",
"description": "説明", "description": "説明",
"appearance": "外観", "appearance": "外観",
"env": "環境変数" "env": "サブフロープロパティ"
}, },
"languages" : { "languages": {
"de": "ドイツ語", "de": "ドイツ語",
"en-US": "英語", "en-US": "英語",
"ja": "日本語", "ja": "日本語",

View File

@ -14,7 +14,8 @@
* limitations under the License. * limitations under the License.
**/ **/
RED.history = (function() { RED.history = (function() {
var undo_history = []; var undoHistory = [];
var redoHistory = [];
function undoEvent(ev) { function undoEvent(ev) {
var i; var i;
@ -22,52 +23,81 @@ RED.history = (function() {
var node; var node;
var subflow; var subflow;
var modifiedTabs = {}; var modifiedTabs = {};
var inverseEv;
if (ev) { if (ev) {
if (ev.t == 'multi') { if (ev.t == 'multi') {
inverseEv = {
t: 'multi',
events: []
};
len = ev.events.length; len = ev.events.length;
for (i=len-1;i>=0;i--) { for (i=len-1;i>=0;i--) {
undoEvent(ev.events[i]); var r = undoEvent(ev.events[i]);
inverseEv.events.push(r);
} }
} else if (ev.t == 'replace') { } else if (ev.t == 'replace') {
inverseEv = {
t: 'replace',
config: RED.nodes.createCompleteNodeSet(),
changed: [],
rev: RED.nodes.version()
};
RED.nodes.clear(); RED.nodes.clear();
var imported = RED.nodes.import(ev.config); var imported = RED.nodes.import(ev.config);
imported[0].forEach(function(n) { imported[0].forEach(function(n) {
if (ev.changed[n.id]) { if (ev.changed[n.id]) {
n.changed = true; n.changed = true;
inverseEv.changed[n.id] = true;
} }
}) })
RED.nodes.version(ev.rev); RED.nodes.version(ev.rev);
} else if (ev.t == 'add') { } else if (ev.t == 'add') {
inverseEv = {
t: "delete",
};
if (ev.nodes) { if (ev.nodes) {
inverseEv.nodes = [];
for (i=0;i<ev.nodes.length;i++) { for (i=0;i<ev.nodes.length;i++) {
node = RED.nodes.node(ev.nodes[i]); node = RED.nodes.node(ev.nodes[i]);
if (node.z) { if (node.z) {
modifiedTabs[node.z] = true; modifiedTabs[node.z] = true;
} }
inverseEv.nodes.push(node);
RED.nodes.remove(ev.nodes[i]); RED.nodes.remove(ev.nodes[i]);
} }
} }
if (ev.links) { if (ev.links) {
inverseEv.links = [];
for (i=0;i<ev.links.length;i++) { for (i=0;i<ev.links.length;i++) {
inverseEv.links.push(ev.links[i]);
RED.nodes.removeLink(ev.links[i]); RED.nodes.removeLink(ev.links[i]);
} }
} }
if (ev.workspaces) { if (ev.workspaces) {
inverseEv.workspaces = [];
for (i=0;i<ev.workspaces.length;i++) { for (i=0;i<ev.workspaces.length;i++) {
var workspaceOrder = RED.nodes.getWorkspaceOrder();
ev.workspaces[i]._index = workspaceOrder.indexOf(ev.workspaces[i].id);
inverseEv.workspaces.push(ev.workspaces[i]);
RED.nodes.removeWorkspace(ev.workspaces[i].id); RED.nodes.removeWorkspace(ev.workspaces[i].id);
RED.workspaces.remove(ev.workspaces[i]); RED.workspaces.remove(ev.workspaces[i]);
} }
} }
if (ev.subflows) { if (ev.subflows) {
inverseEv.subflows = [];
for (i=0;i<ev.subflows.length;i++) { for (i=0;i<ev.subflows.length;i++) {
inverseEv.subflows.push(ev.subflows[i]);
RED.nodes.removeSubflow(ev.subflows[i]); RED.nodes.removeSubflow(ev.subflows[i]);
RED.workspaces.remove(ev.subflows[i]); RED.workspaces.remove(ev.subflows[i]);
} }
} }
if (ev.subflow) { if (ev.subflow) {
inverseEv.subflow = {};
if (ev.subflow.instances) { if (ev.subflow.instances) {
inverseEv.subflow.instances = [];
ev.subflow.instances.forEach(function(n) { ev.subflow.instances.forEach(function(n) {
inverseEv.subflow.instances.push(n);
var node = RED.nodes.node(n.id); var node = RED.nodes.node(n.id);
if (node) { if (node) {
node.changed = n.changed; node.changed = n.changed;
@ -83,21 +113,30 @@ RED.history = (function() {
} }
} }
if (ev.removedLinks) { if (ev.removedLinks) {
inverseEv.createdLinks = [];
for (i=0;i<ev.removedLinks.length;i++) { for (i=0;i<ev.removedLinks.length;i++) {
inverseEv.createdLinks.push(ev.removedLinks[i]);
RED.nodes.addLink(ev.removedLinks[i]); RED.nodes.addLink(ev.removedLinks[i]);
} }
} }
} else if (ev.t == "delete") { } else if (ev.t == "delete") {
inverseEv = {
t: "add"
};
if (ev.workspaces) { if (ev.workspaces) {
inverseEv.workspaces = [];
for (i=0;i<ev.workspaces.length;i++) { for (i=0;i<ev.workspaces.length;i++) {
inverseEv.workspaces.push(ev.workspaces[i]);
RED.nodes.addWorkspace(ev.workspaces[i],ev.workspaces[i]._index); RED.nodes.addWorkspace(ev.workspaces[i],ev.workspaces[i]._index);
RED.workspaces.add(ev.workspaces[i],undefined,ev.workspaces[i]._index); RED.workspaces.add(ev.workspaces[i],undefined,ev.workspaces[i]._index);
delete ev.workspaces[i]._index; delete ev.workspaces[i]._index;
} }
} }
if (ev.subflows) { if (ev.subflows) {
inverseEv.subflows = [];
for (i=0;i<ev.subflows.length;i++) { for (i=0;i<ev.subflows.length;i++) {
inverseEv.subflows.push(ev.subflows[i]);
RED.nodes.addSubflow(ev.subflows[i]); RED.nodes.addSubflow(ev.subflows[i]);
} }
} }
@ -126,8 +165,11 @@ RED.history = (function() {
} }
} }
if (ev.subflow) { if (ev.subflow) {
inverseEv.subflow = {};
if (ev.subflow.hasOwnProperty('instances')) { if (ev.subflow.hasOwnProperty('instances')) {
inverseEv.subflow.instances = [];
ev.subflow.instances.forEach(function(n) { ev.subflow.instances.forEach(function(n) {
inverseEv.subflow.instances.push(n);
var node = RED.nodes.node(n.id); var node = RED.nodes.node(n.id);
if (node) { if (node) {
node.changed = n.changed; node.changed = n.changed;
@ -152,14 +194,25 @@ RED.history = (function() {
}); });
} }
if (ev.nodes) { if (ev.nodes) {
inverseEv.nodes = [];
for (i=0;i<ev.nodes.length;i++) { for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]); RED.nodes.add(ev.nodes[i]);
modifiedTabs[ev.nodes[i].z] = true; modifiedTabs[ev.nodes[i].z] = true;
inverseEv.nodes.push(ev.nodes[i].id);
} }
} }
if (ev.links) { if (ev.links) {
inverseEv.links = [];
for (i=0;i<ev.links.length;i++) { for (i=0;i<ev.links.length;i++) {
RED.nodes.addLink(ev.links[i]); RED.nodes.addLink(ev.links[i]);
inverseEv.links.push(ev.links[i]);
}
}
if (ev.createdLinks) {
inverseEv.removedLinks = [];
for (i=0;i<ev.createdLinks.length;i++) {
inverseEv.removedLinks.push(ev.createdLinks[i]);
RED.nodes.removeLink(ev.createdLinks[i]);
} }
} }
if (ev.changes) { if (ev.changes) {
@ -179,8 +232,14 @@ RED.history = (function() {
} }
} else if (ev.t == "move") { } else if (ev.t == "move") {
inverseEv = {
t: 'move',
nodes: []
};
for (i=0;i<ev.nodes.length;i++) { for (i=0;i<ev.nodes.length;i++) {
var n = ev.nodes[i]; var n = ev.nodes[i];
var rn = {n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.moved};
inverseEv.nodes.push(rn);
n.n.x = n.ox; n.n.x = n.ox;
n.n.y = n.oy; n.n.y = n.oy;
n.n.dirty = true; n.n.dirty = true;
@ -188,18 +247,28 @@ RED.history = (function() {
} }
// A move could have caused a link splice // A move could have caused a link splice
if (ev.links) { if (ev.links) {
inverseEv.removedLinks = [];
for (i=0;i<ev.links.length;i++) { for (i=0;i<ev.links.length;i++) {
inverseEv.removedLinks.push(ev.links[i]);
RED.nodes.removeLink(ev.links[i]); RED.nodes.removeLink(ev.links[i]);
} }
} }
if (ev.removedLinks) { if (ev.removedLinks) {
inverseEv.links = [];
for (i=0;i<ev.removedLinks.length;i++) { for (i=0;i<ev.removedLinks.length;i++) {
inverseEv.links.push(ev.removedLinks[i]);
RED.nodes.addLink(ev.removedLinks[i]); RED.nodes.addLink(ev.removedLinks[i]);
} }
} }
} else if (ev.t == "edit") { } else if (ev.t == "edit") {
inverseEv = {
t: "edit",
changes: {}
};
inverseEv.node = ev.node;
for (i in ev.changes) { for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) { if (ev.changes.hasOwnProperty(i)) {
inverseEv.changes[i] = ev.node[i];
if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) { if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) {
// This is a config node property // This is a config node property
var currentConfigNode = RED.nodes.node(ev.node[i]); var currentConfigNode = RED.nodes.node(ev.node[i]);
@ -219,22 +288,29 @@ RED.history = (function() {
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!ev.node.disabled); $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!ev.node.disabled);
} }
if (ev.subflow) { if (ev.subflow) {
inverseEv.subflow = {};
if (ev.subflow.hasOwnProperty('inputCount')) { if (ev.subflow.hasOwnProperty('inputCount')) {
inverseEv.subflow.inputCount = ev.node.in.length;
if (ev.node.in.length > ev.subflow.inputCount) { if (ev.node.in.length > ev.subflow.inputCount) {
inverseEv.subflow.inputs = ev.node.in.slice(ev.subflow.inputCount);
ev.node.in.splice(ev.subflow.inputCount); ev.node.in.splice(ev.subflow.inputCount);
} else if (ev.subflow.inputs.length > 0) { } else if (ev.subflow.inputs.length > 0) {
ev.node.in = ev.node.in.concat(ev.subflow.inputs); ev.node.in = ev.node.in.concat(ev.subflow.inputs);
} }
} }
if (ev.subflow.hasOwnProperty('outputCount')) { if (ev.subflow.hasOwnProperty('outputCount')) {
inverseEv.subflow.outputCount = ev.node.out.length;
if (ev.node.out.length > ev.subflow.outputCount) { if (ev.node.out.length > ev.subflow.outputCount) {
inverseEv.subflow.outputs = ev.node.out.slice(ev.subflow.outputCount);
ev.node.out.splice(ev.subflow.outputCount); ev.node.out.splice(ev.subflow.outputCount);
} else if (ev.subflow.outputs.length > 0) { } else if (ev.subflow.outputs.length > 0) {
ev.node.out = ev.node.out.concat(ev.subflow.outputs); ev.node.out = ev.node.out.concat(ev.subflow.outputs);
} }
} }
if (ev.subflow.hasOwnProperty('instances')) { if (ev.subflow.hasOwnProperty('instances')) {
inverseEv.subflow.instances = [];
ev.subflow.instances.forEach(function(n) { ev.subflow.instances.forEach(function(n) {
inverseEv.subflow.instances.push(n);
var node = RED.nodes.node(n.id); var node = RED.nodes.node(n.id);
if (node) { if (node) {
node.changed = n.changed; node.changed = n.changed;
@ -258,9 +334,11 @@ RED.history = (function() {
var outputMap; var outputMap;
if (ev.outputMap) { if (ev.outputMap) {
outputMap = {}; outputMap = {};
inverseEv.outputMap = {};
for (var port in ev.outputMap) { for (var port in ev.outputMap) {
if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== "-1") { if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== "-1") {
outputMap[ev.outputMap[port]] = port; outputMap[ev.outputMap[port]] = port;
inverseEv.outputMap[ev.outputMap[port]] = port;
} }
} }
} }
@ -268,39 +346,107 @@ RED.history = (function() {
RED.editor.validateNode(ev.node); RED.editor.validateNode(ev.node);
} }
if (ev.links) { if (ev.links) {
inverseEv.createdLinks = [];
for (i=0;i<ev.links.length;i++) { for (i=0;i<ev.links.length;i++) {
RED.nodes.addLink(ev.links[i]); RED.nodes.addLink(ev.links[i]);
inverseEv.createdLinks.push(ev.links[i]);
}
}
if (ev.createdLinks) {
inverseEv.links = [];
for (i=0;i<ev.createdLinks.length;i++) {
RED.nodes.removeLink(ev.createdLinks[i]);
inverseEv.links.push(ev.createdLinks[i]);
} }
} }
ev.node.dirty = true; ev.node.dirty = true;
ev.node.changed = ev.changed; ev.node.changed = ev.changed;
} else if (ev.t == "createSubflow") { } else if (ev.t == "createSubflow") {
inverseEv = {
t: "deleteSubflow",
activeWorkspace: ev.activeWorkspace,
dirty: RED.nodes.dirty()
};
if (ev.nodes) { if (ev.nodes) {
inverseEv.movedNodes = [];
var z = ev.activeWorkspace;
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) { RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) {
n.x += ev.subflow.offsetX; n.x += ev.subflow.offsetX;
n.y += ev.subflow.offsetY; n.y += ev.subflow.offsetY;
n.z = ev.activeWorkspace;
n.dirty = true; n.dirty = true;
inverseEv.movedNodes.push(n.id);
RED.nodes.moveNodeToTab(n, z);
}); });
inverseEv.subflows = [];
for (i=0;i<ev.nodes.length;i++) { for (i=0;i<ev.nodes.length;i++) {
inverseEv.subflows.push(RED.nodes.node(ev.nodes[i]));
RED.nodes.remove(ev.nodes[i]); RED.nodes.remove(ev.nodes[i]);
} }
} }
if (ev.links) { if (ev.links) {
inverseEv.links = [];
for (i=0;i<ev.links.length;i++) { for (i=0;i<ev.links.length;i++) {
inverseEv.links.push(ev.links[i]);
RED.nodes.removeLink(ev.links[i]); RED.nodes.removeLink(ev.links[i]);
} }
} }
inverseEv.subflow = ev.subflow;
RED.nodes.removeSubflow(ev.subflow.subflow); RED.nodes.removeSubflow(ev.subflow.subflow);
RED.workspaces.remove(ev.subflow.subflow); RED.workspaces.remove(ev.subflow.subflow);
if (ev.removedLinks) { if (ev.removedLinks) {
inverseEv.createdLinks = [];
for (i=0;i<ev.removedLinks.length;i++) { for (i=0;i<ev.removedLinks.length;i++) {
inverseEv.createdLinks.push(ev.removedLinks[i]);
RED.nodes.addLink(ev.removedLinks[i]); RED.nodes.addLink(ev.removedLinks[i]);
} }
} }
} else if (ev.t == "deleteSubflow") {
inverseEv = {
t: "createSubflow",
activeWorkspace: ev.activeWorkspace,
dirty: RED.nodes.dirty(),
};
if (ev.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow);
inverseEv.subflow = ev.subflow;
}
if (ev.subflows) {
inverseEv.nodes = [];
for (i=0;i<ev.subflows.length;i++) {
RED.nodes.add(ev.subflows[i]);
inverseEv.nodes.push(ev.subflows[i].id);
}
}
if (ev.movedNodes) {
ev.movedNodes.forEach(function(nid) {
nn = RED.nodes.node(nid);
nn.x -= ev.subflow.offsetX;
nn.y -= ev.subflow.offsetY;
nn.dirty = true;
RED.nodes.moveNodeToTab(nn, ev.subflow.subflow.id);
});
}
if (ev.links) {
inverseEv.links = [];
for (i=0;i<ev.links.length;i++) {
inverseEv.links.push(ev.links[i]);
RED.nodes.addLink(ev.links[i]);
}
}
if (ev.createdLinks) {
inverseEv.removedLinks = [];
for (i=0;i<ev.createdLinks.length;i++) {
inverseEv.removedLinks.push(ev.createdLinks[i]);
RED.nodes.removeLink(ev.createdLinks[i]);
}
}
} else if (ev.t == "reorder") { } else if (ev.t == "reorder") {
inverseEv = {
t: 'reorder',
order: RED.nodes.getWorkspaceOrder()
};
if (ev.order) { if (ev.order) {
RED.workspaces.order(ev.order); RED.workspaces.order(ev.order);
} }
@ -320,6 +466,8 @@ RED.history = (function() {
RED.workspaces.refresh(); RED.workspaces.refresh();
RED.sidebar.config.refresh(); RED.sidebar.config.refresh();
RED.subflow.refresh(); RED.subflow.refresh();
return inverseEv;
} }
} }
@ -327,28 +475,42 @@ RED.history = (function() {
return { return {
//TODO: this function is a placeholder until there is a 'save' event that can be listened to //TODO: this function is a placeholder until there is a 'save' event that can be listened to
markAllDirty: function() { markAllDirty: function() {
for (var i=0;i<undo_history.length;i++) { for (var i=0;i<undoHistory.length;i++) {
undo_history[i].dirty = true; undoHistory[i].dirty = true;
} }
}, },
list: function() { list: function() {
return undo_history return undoHistory;
}, },
depth: function() { depth: function() {
return undo_history.length; return undoHistory.length;
}, },
push: function(ev) { push: function(ev) {
undo_history.push(ev); undoHistory.push(ev);
redoHistory = [];
}, },
pop: function() { pop: function() {
var ev = undo_history.pop(); var ev = undoHistory.pop();
undoEvent(ev); var rev = undoEvent(ev);
if (rev) {
redoHistory.push(rev);
}
}, },
peek: function() { peek: function() {
return undo_history[undo_history.length-1]; return undoHistory[undoHistory.length-1];
}, },
clear: function() { clear: function() {
undo_history = []; undoHistory = [];
redoHistory = [];
},
redo: function() {
var ev = redoHistory.pop();
if (ev) {
var uev = undoEvent(ev);
if (uev) {
undoHistory.push(uev);
}
}
} }
} }

View File

@ -50,6 +50,19 @@ RED.i18n = (function() {
} }
}, },
lang: function() {
// Gets the active message catalog language. This is based on what
// locale the editor is using and what languages are available.
//
var preferredLangs = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
var knownLangs = RED.settings.theme("languages")||["en-US"];
for (var i=0;i<preferredLangs.length;i++) {
if (knownLangs.indexOf(preferredLangs[i]) > -1) {
return preferredLangs[i]
}
}
return 'end-US'
},
loadNodeCatalog: function(namespace,done) { loadNodeCatalog: function(namespace,done) {
var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage()); var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
var toLoad = languageList.length; var toLoad = languageList.length;

View File

@ -30,7 +30,8 @@
"backspace": "core:delete-config-selection", "backspace": "core:delete-config-selection",
"delete": "core:delete-config-selection", "delete": "core:delete-config-selection",
"ctrl-a": "core:select-all-config-nodes", "ctrl-a": "core:select-all-config-nodes",
"ctrl-z": "core:undo" "ctrl-z": "core:undo",
"ctrl-y": "core:redo"
}, },
"red-ui-workspace": { "red-ui-workspace": {
"backspace": "core:delete-selection", "backspace": "core:delete-selection",
@ -40,6 +41,7 @@
"ctrl-x": "core:cut-selection-to-internal-clipboard", "ctrl-x": "core:cut-selection-to-internal-clipboard",
"ctrl-v": "core:paste-from-internal-clipboard", "ctrl-v": "core:paste-from-internal-clipboard",
"ctrl-z": "core:undo", "ctrl-z": "core:undo",
"ctrl-y": "core:redo",
"ctrl-a": "core:select-all-nodes", "ctrl-a": "core:select-all-nodes",
"shift-?": "core:show-help", "shift-?": "core:show-help",
"up": "core:move-selection-up", "up": "core:move-selection-up",

View File

@ -17,6 +17,8 @@ RED.nodes = (function() {
var node_defs = {}; var node_defs = {};
var nodes = []; var nodes = [];
var nodeTabMap = {};
var configNodes = {}; var configNodes = {};
var links = []; var links = [];
var defaultWorkspace; var defaultWorkspace;
@ -213,6 +215,11 @@ RED.nodes = (function() {
n.i = nextId+1; n.i = nextId+1;
} }
nodes.push(n); nodes.push(n);
if (nodeTabMap[n.z]) {
nodeTabMap[n.z][n.id] = n;
} else {
console.warn("Node added to unknown tab/subflow:",n);
}
} }
RED.events.emit('nodes:add',n); RED.events.emit('nodes:add',n);
} }
@ -246,6 +253,9 @@ RED.nodes = (function() {
node = getNode(id); node = getNode(id);
if (node) { if (node) {
nodes.splice(nodes.indexOf(node),1); nodes.splice(nodes.indexOf(node),1);
if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id];
}
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); }); removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
removedLinks.forEach(function(l) {links.splice(links.indexOf(l), 1); }); removedLinks.forEach(function(l) {links.splice(links.indexOf(l), 1); });
var updatedConfigNode = false; var updatedConfigNode = false;
@ -291,6 +301,17 @@ RED.nodes = (function() {
return {links:removedLinks,nodes:removedNodes}; return {links:removedLinks,nodes:removedNodes};
} }
function moveNodeToTab(node, z) {
if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id];
}
if (!nodeTabMap[z]) {
nodeTabMap[z] = {};
}
nodeTabMap[z][node.id] = node;
node.z = z;
}
function removeLink(l) { function removeLink(l) {
var index = links.indexOf(l); var index = links.indexOf(l);
if (index != -1) { if (index != -1) {
@ -300,6 +321,8 @@ RED.nodes = (function() {
function addWorkspace(ws,targetIndex) { function addWorkspace(ws,targetIndex) {
workspaces[ws.id] = ws; workspaces[ws.id] = ws;
nodeTabMap[ws.id] = {};
ws._def = RED.nodes.getType('tab'); ws._def = RED.nodes.getType('tab');
if (targetIndex === undefined) { if (targetIndex === undefined) {
workspacesOrder.push(ws.id); workspacesOrder.push(ws.id);
@ -312,6 +335,7 @@ RED.nodes = (function() {
} }
function removeWorkspace(id) { function removeWorkspace(id) {
delete workspaces[id]; delete workspaces[id];
delete nodeTabMap[id];
workspacesOrder.splice(workspacesOrder.indexOf(id),1); workspacesOrder.splice(workspacesOrder.indexOf(id),1);
var removedNodes = []; var removedNodes = [];
@ -357,6 +381,8 @@ RED.nodes = (function() {
sf.name = subflowName; sf.name = subflowName;
} }
subflows[sf.id] = sf; subflows[sf.id] = sf;
nodeTabMap[sf.id] = {};
RED.nodes.registerType("subflow:"+sf.id, { RED.nodes.registerType("subflow:"+sf.id, {
defaults:{ defaults:{
name:{value:""}, name:{value:""},
@ -373,14 +399,14 @@ RED.nodes = (function() {
inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null },
outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null },
oneditresize: function(size) { oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); // var rows = $(".dialog-form>div:not(.node-input-env-container-row)");
var height = size.height; var height = size.height;
for (var i=0; i<rows.size(); i++) { // for (var i=0; i<rows.size(); i++) {
height -= $(rows[i]).outerHeight(true); // height -= $(rows[i]).outerHeight(true);
} // }
var editorRow = $("#dialog-form>div.node-input-env-container-row"); // var editorRow = $("#dialog-form>div.node-input-env-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-env-container").editableList('height',height-80); $("ol.red-ui-editor-subflow-env-list").editableList('height',height);
}, },
set:{ set:{
module: "node-red" module: "node-red"
@ -393,6 +419,7 @@ RED.nodes = (function() {
} }
function removeSubflow(sf) { function removeSubflow(sf) {
delete subflows[sf.id]; delete subflows[sf.id];
delete nodeTabMap[sf.id];
registry.removeNodeType("subflow:"+sf.id); registry.removeNodeType("subflow:"+sf.id);
} }
@ -1267,12 +1294,13 @@ RED.nodes = (function() {
// TODO: supports filter.z|type // TODO: supports filter.z|type
function filterNodes(filter) { function filterNodes(filter) {
var result = []; var result = [];
var searchSet = nodes;
if (filter.hasOwnProperty("z") && Object.hasOwnProperty("values") && nodeTabMap.hasOwnProperty(filter.z) ) {
searchSet = Object.values(nodeTabMap[filter.z]);
}
for (var n=0;n<nodes.length;n++) { for (var n=0;n<searchSet.length;n++) {
var node = nodes[n]; var node = searchSet[n];
if (filter.hasOwnProperty("z") && node.z !== filter.z) {
continue;
}
if (filter.hasOwnProperty("type") && node.type !== filter.type) { if (filter.hasOwnProperty("type") && node.type !== filter.type) {
continue; continue;
} }
@ -1340,6 +1368,7 @@ RED.nodes = (function() {
function clear() { function clear() {
nodes = []; nodes = [];
links = []; links = [];
nodeTabMap = {};
configNodes = {}; configNodes = {};
workspacesOrder = []; workspacesOrder = [];
var subflowIds = Object.keys(subflows); var subflowIds = Object.keys(subflows);
@ -1449,6 +1478,8 @@ RED.nodes = (function() {
remove: removeNode, remove: removeNode,
clear: clear, clear: clear,
moveNodeToTab: moveNodeToTab,
addLink: addLink, addLink: addLink,
removeLink: removeLink, removeLink: removeLink,

View File

@ -34,6 +34,8 @@
* - addItem(itemData) * - addItem(itemData)
* - insertItemAt : function(data,index) - add an item at the specified index * - insertItemAt : function(data,index) - add an item at the specified index
* - removeItem(itemData) * - removeItem(itemData)
* - getItemAt(index)
* - indexOf(itemData)
* - width(width) * - width(width)
* - height(height) * - height(height)
* - items() * - items()
@ -186,7 +188,11 @@
} }
}, },
_destroy: function() { _destroy: function() {
this.topContainer.remove(); if (this.topContainer) {
var tc = this.topContainer;
delete this.topContainer;
tc.remove();
}
}, },
_refreshFilter: function() { _refreshFilter: function() {
var that = this; var that = this;
@ -232,6 +238,23 @@
this.uiHeight = desiredHeight; this.uiHeight = desiredHeight;
this._resize(); this._resize();
}, },
getItemAt: function(index) {
var items = this.items();
if (index >= 0 && index < items.length) {
return $(items[index]).data('data');
} else {
return;
}
},
indexOf: function(data) {
var items = this.items();
for (var i=0;i<items.length;i++) {
if ($(items[i]).data('data') === data) {
return i
}
}
return -1
},
insertItemAt: function(data,index) { insertItemAt: function(data,index) {
var that = this; var that = this;
data = data || {}; data = data || {};

View File

@ -225,7 +225,7 @@ RED.menu = (function() {
triggerAction(opt.id,state); triggerAction(opt.id,state);
} }
} }
if (!alreadySet) { if (!opt.local && !alreadySet) {
RED.settings.set(opt.setting||("menu-"+opt.id), state); RED.settings.set(opt.setting||("menu-"+opt.id), state);
} }
} }

View File

@ -253,6 +253,71 @@ RED.popover = (function() {
content: label, content: label,
delay: { show: 750, hide: 50 } delay: { show: 750, hide: 50 }
}); });
},
panel: function(content) {
var panel = $('<div class="red-ui-editor-dialog red-ui-popover-panel"></div>');
panel.css({ display: "none" });
panel.appendTo(document.body);
content.appendTo(panel);
var closeCallback;
function hide() {
$(document).off("mousedown.red-ui-popover-panel-close");
panel.hide();
panel.css({
height: "auto"
});
panel.remove();
}
function show(options) {
var closeCallback = options.onclose;
var target = options.target;
var align = options.align || "left";
var pos = target.offset();
var targetWidth = target.width();
var targetHeight = target.height();
var panelHeight = panel.height();
var panelWidth = panel.width();
var top = (targetHeight+pos.top);
if (top+panelHeight > $(window).height()) {
top -= (top+panelHeight)-$(window).height() + 5;
}
if (top < 0) {
panelHeight.height(panelHeight+top)
top = 0;
}
if (align === "left") {
panel.css({
top: top+"px",
left: (pos.left)+"px",
});
} else if(align === "right") {
panel.css({
top: top+"px",
left: (pos.left-panelWidth)+"px",
});
}
panel.slideDown(100);
$(document).on("mousedown.red-ui-popover-panel-close", function(event) {
if(!$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) {
if (closeCallback) {
closeCallback();
}
hide();
}
// if ($(event.target).closest(target).length) {
// event.preventDefault();
// }
})
}
return {
container: panel,
show:show,
hide:hide
}
} }
} }

View File

@ -81,7 +81,7 @@
} }
}, },
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"}, re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
date: {value:"date",label:"timestamp",hasValue:false}, date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false},
jsonata: { jsonata: {
value: "jsonata", value: "jsonata",
label: "expression", label: "expression",
@ -298,7 +298,8 @@
that.uiSelect.addClass('red-ui-typedInput-focus'); that.uiSelect.addClass('red-ui-typedInput-focus');
}); });
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect); this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
this.type(this.options.default||this.typeList[0].value); this.type(this.options.default||this.typeList[0].value);
}catch(err) { }catch(err) {
console.log(err.stack); console.log(err.stack);
@ -336,6 +337,17 @@
menu.css({ menu.css({
height: "auto" height: "auto"
}); });
if (menu.opts.multiple) {
var selected = [];
menu.find('input[type="checkbox"]').each(function() {
if ($(this).prop("checked")) {
selected.push($(this).data('value'))
}
})
menu.callback(selected);
}
if (this.elementDiv.is(":visible")) { if (this.elementDiv.is(":visible")) {
this.input.trigger("focus"); this.input.trigger("focus");
} else if (this.optionSelectTrigger.is(":visible")){ } else if (this.optionSelectTrigger.is(":visible")){
@ -344,10 +356,12 @@
this.selectTrigger.trigger("focus"); this.selectTrigger.trigger("focus");
} }
}, },
_createMenu: function(opts,callback) { _createMenu: function(menuOptions,opts,callback) {
var that = this; var that = this;
var menu = $("<div>").addClass("red-ui-typedInput-options"); var menu = $("<div>").addClass("red-ui-typedInput-options red-ui-editor-dialog");
opts.forEach(function(opt) { menu.opts = opts;
menu.callback = callback;
menuOptions.forEach(function(opt) {
if (typeof opt === 'string') { if (typeof opt === 'string') {
opt = {value:opt,label:opt}; opt = {value:opt,label:opt};
} }
@ -369,12 +383,20 @@
if (!opt.icon && !opt.label) { if (!opt.icon && !opt.label) {
op.text(opt.value); op.text(opt.value);
} }
var cb;
if (opts.multiple) {
cb = $('<input type="checkbox">').css("pointer-events","none").data('value',opt.value).prependTo(op).on("mousedown", function(evt) { evt.preventDefault() });
}
op.on("click", function(event) { op.on("click", function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
callback(opt.value); if (!opts.multiple) {
that._hideMenu(menu); callback(opt.value);
that._hideMenu(menu);
} else {
cb.prop("checked",!cb.prop("checked"));
}
}); });
}); });
menu.css({ menu.css({
@ -398,9 +420,6 @@
} }
evt.stopPropagation(); evt.stopPropagation();
}) })
return menu; return menu;
}, },
@ -409,11 +428,22 @@
this.disarmClick = false; this.disarmClick = false;
return return
} }
if (menu.opts.multiple) {
var selected = {};
this.value().split(",").forEach(function(f) {
selected[f] = true;
})
menu.find('input[type="checkbox"]').each(function() {
$(this).prop("checked",selected[$(this).data('value')])
})
}
var that = this; var that = this;
var pos = relativeTo.offset(); var pos = relativeTo.offset();
var height = relativeTo.height(); var height = relativeTo.height();
var menuHeight = menu.height(); var menuHeight = menu.height();
var top = (height+pos.top-3); var top = (height+pos.top);
if (top+menuHeight > $(window).height()) { if (top+menuHeight > $(window).height()) {
top -= (top+menuHeight)-$(window).height()+5; top -= (top+menuHeight)-$(window).height()+5;
} }
@ -423,7 +453,7 @@
} }
menu.css({ menu.css({
top: top+"px", top: top+"px",
left: (2+pos.left)+"px", left: (pos.left)+"px",
}); });
menu.slideDown(100); menu.slideDown(100);
this._delay(function() { this._delay(function() {
@ -471,7 +501,7 @@
this._getLabelWidth(this.selectTrigger, function(labelWidth) { this._getLabelWidth(this.selectTrigger, function(labelWidth) {
that.elementDiv.css('left',labelWidth+"px"); that.elementDiv.css('left',labelWidth+"px");
that.valueLabelContainer.css('left',labelWidth+"px"); that.valueLabelContainer.css('left',labelWidth+"px");
if (that.optionExpandButton.is(":visible")) { if (that.optionExpandButton.shown) {
that.elementDiv.css('right',"22px"); that.elementDiv.css('right',"22px");
that.valueLabelContainer.css('right',"22px"); that.valueLabelContainer.css('right',"22px");
} else { } else {
@ -496,7 +526,7 @@
} else { } else {
that.optionSelectLabel.css({'left':'0'}) that.optionSelectLabel.css({'left':'0'})
that.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'}); that.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'});
if (!that.optionExpandButton.is(":visible")) { if (!that.optionExpandButton.shown) {
that.elementDiv.css({'right':0}); that.elementDiv.css({'right':0});
that.input.css({ that.input.css({
'border-top-right-radius': '4px', 'border-top-right-radius': '4px',
@ -511,25 +541,35 @@
_updateOptionSelectLabel: function(o) { _updateOptionSelectLabel: function(o) {
var opt = this.typeMap[this.propertyType]; var opt = this.typeMap[this.propertyType];
this.optionSelectLabel.empty(); this.optionSelectLabel.empty();
if (o.icon) { if (this.typeMap[this.propertyType].valueLabel) {
if (o.icon.indexOf("<") === 0) { if (opt.multiple) {
$(o.icon).prependTo(this.optionSelectLabel); this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o);
} else if (o.icon.indexOf("/") !== -1) {
// url
$('<img>',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel);
} else { } else {
// icon class this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o.value);
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); }
} else if (!opt.multiple) {
if (o.icon) {
if (o.icon.indexOf("<") === 0) {
$(o.icon).prependTo(this.optionSelectLabel);
} else if (o.icon.indexOf("/") !== -1) {
// url
$('<img>',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel);
} else {
// icon class
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
}
} else if (o.label) {
this.optionSelectLabel.text(o.label);
} else {
this.optionSelectLabel.text(o.value);
}
if (opt.hasValue) {
this.optionValue = o.value;
this._resize();
this.input.trigger('change',this.propertyType,this.value());
} }
} else if (o.label) {
this.optionSelectLabel.text(o.label);
} else { } else {
this.optionSelectLabel.text(o.value); this.optionSelectLabel.text(o.length+" selected");
}
if (opt.hasValue) {
this.optionValue = o.value;
this._resize();
this.input.trigger('change',this.propertyType,this.value());
} }
}, },
_destroy: function() { _destroy: function() {
@ -558,7 +598,7 @@
if (this.menu) { if (this.menu) {
this.menu.remove(); this.menu.remove();
} }
this.menu = this._createMenu(this.typeList, function(v) { that.type(v) }); this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) { if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value); this.type(this.typeList[0].value);
} else { } else {
@ -572,38 +612,51 @@
this._resize(); this._resize();
}, },
value: function(value) { value: function(value) {
var that = this;
var opt = this.typeMap[this.propertyType];
if (!arguments.length) { if (!arguments.length) {
var v = this.input.val(); var v = this.input.val();
if (this.typeMap[this.propertyType].export) { if (opt.export) {
v = this.typeMap[this.propertyType].export(v,this.optionValue) v = opt.export(v,this.optionValue)
} }
return v; return v;
} else { } else {
var selectedOption; var selectedOption = [];
if (this.typeMap[this.propertyType].options) { if (opt.options) {
for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) { var checkValues = [value];
var op = this.typeMap[this.propertyType].options[i]; if (opt.multiple) {
if (typeof op === "string") { selectedOption = [];
if (op === value) { checkValues = value.split(",");
selectedOption = this.activeOptions[op]; }
checkValues.forEach(function(value) {
for (var i=0;i<opt.options.length;i++) {
var op = opt.options[i];
if (typeof op === "string") {
if (op === value) {
selectedOption.push(that.activeOptions[op]);
break;
}
} else if (op.value === value) {
selectedOption.push(op);
break; break;
} }
} else if (op.value === value) {
selectedOption = op;
break;
} }
} })
if (!selectedOption) {
selectedOption = {value:""}
}
this.input.val(value); this.input.val(value);
this._updateOptionSelectLabel(selectedOption) if (!opt.multiple) {
if (!selectedOption.length === 0) {
selectedOption = [{value:""}];
}
this._updateOptionSelectLabel(selectedOption[0])
} else {
this._updateOptionSelectLabel(selectedOption)
}
} else { } else {
this.input.val(value); this.input.val(value);
} if (opt.valueLabel) {
if (this.typeMap[this.propertyType].valueLabel) { this.valueLabelContainer.empty();
this.valueLabelContainer.empty(); opt.valueLabel.call(this,this.valueLabelContainer,value);
this.typeMap[this.propertyType].valueLabel.call(this,this.valueLabelContainer,value); }
} }
this.input.trigger('change',this.type(),value); this.input.trigger('change',this.type(),value);
} }
@ -621,7 +674,7 @@
} }
this.selectLabel.empty(); this.selectLabel.empty();
var image; var image;
if (opt.icon) { if (opt.icon && opt.showLabel !== false) {
if (opt.icon.indexOf("<") === 0) { if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(this.selectLabel); $(opt.icon).prependTo(this.selectLabel);
} }
@ -636,7 +689,8 @@
else { else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); $('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel);
} }
} else { }
if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
this.selectLabel.text(opt.label); this.selectLabel.text(opt.label);
} }
if (this.optionMenu) { if (this.optionMenu) {
@ -646,6 +700,7 @@
if (opt.options) { if (opt.options) {
if (this.optionExpandButton) { if (this.optionExpandButton) {
this.optionExpandButton.hide(); this.optionExpandButton.hide();
this.optionExpandButton.shown = false;
} }
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.show(); this.optionSelectTrigger.show();
@ -668,36 +723,50 @@
if (!that.activeOptions.hasOwnProperty(that.optionValue)) { if (!that.activeOptions.hasOwnProperty(that.optionValue)) {
that.optionValue = null; that.optionValue = null;
} }
this.optionMenu = this._createMenu(opt.options,function(v){
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
that.value(that.activeOptions[v].value)
}
});
var op; var op;
if (!opt.hasValue) { if (!opt.hasValue) {
var currentVal = this.input.val();
var validValue = false; var validValue = false;
for (var i=0;i<opt.options.length;i++) { var currentVal = this.input.val();
op = opt.options[i]; if (!opt.multiple) {
if (typeof op === "string" && op === currentVal) { for (var i=0;i<opt.options.length;i++) {
that._updateOptionSelectLabel({value:currentVal}); op = opt.options[i];
validValue = true; if (typeof op === "string" && op === currentVal) {
break; that._updateOptionSelectLabel({value:currentVal});
} else if (op.value === currentVal) { validValue = true;
that._updateOptionSelectLabel(op); break;
validValue = true; } else if (op.value === currentVal) {
break; that._updateOptionSelectLabel(op);
validValue = true;
break;
}
} }
} if (!validValue) {
if (!validValue) { op = opt.options[0];
op = opt.options[0]; if (typeof op === "string") {
if (typeof op === "string") { this.value(op);
this.value(op); that._updateOptionSelectLabel({value:op});
that._updateOptionSelectLabel({value:op}); } else {
} else { this.value(op.value);
this.value(op.value); that._updateOptionSelectLabel(op);
that._updateOptionSelectLabel(op); }
}
} else {
// Check to see if value is a valid csv of
// options.
var currentValues = {};
currentVal.split(",").forEach(function(v) {
if (v) {
currentValues[v] = true;
}
});
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
delete currentValues[op.value||op];
}
if (!$.isEmptyObject(currentValues)) {
// Invalid, set to default/empty
this.value((opt.default||[]).join(","));
} }
} }
} else { } else {
@ -733,7 +802,21 @@
this.optionSelectTrigger.hide(); this.optionSelectTrigger.hide();
} }
} }
this.optionMenu = this._createMenu(opt.options,opt,function(v){
if (!opt.multiple) {
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
that.value(that.activeOptions[v].value)
}
} else {
that._updateOptionSelectLabel(v);
if (!opt.hasValue) {
that.value(v.join(","))
}
}
});
} }
this._trigger("typechange",null,this.propertyType);
this.input.trigger('change',this.propertyType,this.value()); this.input.trigger('change',this.propertyType,this.value());
} else { } else {
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
@ -758,17 +841,44 @@
this.elementDiv.show(); this.elementDiv.show();
} }
if (this.optionExpandButton) { if (this.optionExpandButton) {
if (opt.expand && typeof opt.expand === 'function') { if (opt.expand) {
if (opt.expand.icon) {
this.optionExpandButtonIcon.removeClass().addClass("red-ui-typedInput-icon fa "+opt.expand.icon)
} else {
this.optionExpandButtonIcon.removeClass().addClass("red-ui-typedInput-icon fa fa-ellipsis-h")
}
this.optionExpandButton.shown = true;
this.optionExpandButton.show(); this.optionExpandButton.show();
this.optionExpandButton.off('click'); this.optionExpandButton.off('click');
this.optionExpandButton.on('click',function(evt) { this.optionExpandButton.on('click',function(evt) {
evt.preventDefault(); evt.preventDefault();
opt.expand.call(that); if (typeof opt.expand === 'function') {
opt.expand.call(that);
} else {
var container = $('<div>');
var content = opt.expand.content.call(that,container);
var panel = RED.popover.panel(container);
panel.container.css({
width:that.valueLabelContainer.width()
});
if (opt.expand.minWidth) {
panel.container.css({
minWidth: opt.expand.minWidth+"px"
});
}
panel.show({
target:that.optionExpandButton,
onclose:content.onclose,
align: "right"
});
}
}) })
} else { } else {
this.optionExpandButton.shown = false;
this.optionExpandButton.hide(); this.optionExpandButton.hide();
} }
} }
this._trigger("typechange",null,this.propertyType);
this.input.trigger('change',this.propertyType,this.value()); this.input.trigger('change',this.propertyType,this.value());
} }
if (!image) { if (!image) {

View File

@ -19,7 +19,6 @@
*/ */
RED.editor = (function() { RED.editor = (function() {
var editStack = []; var editStack = [];
var editing_node = null; var editing_node = null;
var editing_config_node = null; var editing_config_node = null;
@ -526,7 +525,7 @@ RED.editor = (function() {
} else if (node.type.indexOf("subflow:")===0) { } else if (node.type.indexOf("subflow:")===0) {
var subflow = RED.nodes.subflow(node.type.substring(8)); var subflow = RED.nodes.subflow(node.type.substring(8));
label = RED._("subflow.editSubflowInstance",{name:RED.utils.sanitize(subflow.name)}) label = RED._("subflow.editSubflowInstance",{name:RED.utils.sanitize(subflow.name)})
} else { } else if (node._def !== undefined) {
if (typeof node._def.paletteLabel !== "undefined") { if (typeof node._def.paletteLabel !== "undefined") {
try { try {
label = RED.utils.sanitize((typeof node._def.paletteLabel === "function" ? node._def.paletteLabel.call(node._def) : node._def.paletteLabel)||""); label = RED.utils.sanitize((typeof node._def.paletteLabel === "function" ? node._def.paletteLabel.call(node._def) : node._def.paletteLabel)||"");
@ -546,148 +545,7 @@ RED.editor = (function() {
return label; return label;
} }
function buildEnvForm(container, node) { function isSameObj(env0, env1) {
var env_container = $('#node-input-env-container');
env_container
.css({
'min-height':'150px',
'min-width':'450px'
})
.editableList({
addItem: function(container, i, opt) {
var row = $('<div/>').appendTo(container);
if (opt.parent) {
$('<div/>', {
class:"uneditable-input",
style: "margin-left: 5px; width: calc(40% - 8px)",
}).appendTo(row).text(opt.name);
} else {
$('<input/>', {
class: "node-input-env-name",
type: "text",
style: "margin-left: 5px; width: calc(40% - 8px)",
placeholder: RED._("common.label.name")
}).attr("autocomplete","disable").appendTo(row).val(opt.name);
}
var valueField = $('<input/>',{
class: "node-input-env-value",
type: "text",
style: "margin-left: 5px; width: calc(60% - 8px)"
}).attr("autocomplete","disable").appendTo(row)
valueField.typedInput({default:'str',
types:['str','num','bool','json','bin','env']
});
valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type);
valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value);
var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(container);
$('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
container.parent().addClass("red-ui-editableList-item-removable");
if (opt.parent) {
if ((opt.value !== undefined) && (opt.value !== opt.parent.value || opt.type !== opt.parent.type)) {
actionButton.show();
} else {
actionButton.hide();
}
var restoreTip = RED.popover.tooltip(actionButton,RED._("subflow.env.restore"));
valueField.on("change", function(evt) {
var newType = valueField.typedInput('type');
var newValue = valueField.typedInput('value');
if (newType === opt.parent.type && newValue === opt.parent.value) {
actionButton.hide();
} else {
actionButton.show();
}
})
actionButton.on("click", function(evt) {
evt.preventDefault();
restoreTip.close();
valueField.typedInput('type', opt.parent.type);
valueField.typedInput('value', opt.parent.value);
})
} else {
var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
actionButton.on("click", function(evt) {
evt.preventDefault();
removeTip.close();
container.parent().addClass("red-ui-editableList-item-deleting")
container.fadeOut(300, function() {
env_container.editableList('removeItem',opt);
});
});
}
},
sortable: false,
removable: false
});
var parentEnv = {};
var envList = [];
if (/^subflow:/.test(node.type)) {
var subflowDef = RED.nodes.subflow(node.type.substring(8));
if (subflowDef.env) {
subflowDef.env.forEach(function(env) {
var item = {
name:env.name,
parent: {
type: env.type,
value: env.value
}
}
envList.push(item);
parentEnv[env.name] = item;
})
}
}
if (node.env) {
for (var i = 0; i < node.env.length; i++) {
var env = node.env[i];
if (parentEnv.hasOwnProperty(env.name)) {
parentEnv[env.name].type = env.type;
parentEnv[env.name].value = env.value;
} else {
envList.push({
name: env.name,
type: env.type,
value: env.value
});
}
}
}
envList.forEach(function(env) {
env_container.editableList('addItem', env);
})
}
function exportEnvList(list) {
if (list) {
var env = [];
list.each(function(i) {
var entry = $(this);
var item = entry.data('data');
var name = (item.parent?item.name:entry.find(".node-input-env-name").val()).trim();
if (name !== "") {
var valueInput = entry.find(".node-input-env-value");
var value = valueInput.typedInput("value");
var type = valueInput.typedInput("type");
if (!item.parent || (item.parent.value !== value || item.parent.type !== type)) {
var item = {
name: name,
type: type,
value: value
};
env.push(item);
}
}
});
return env;
}
return null;
}
function isSameEnv(env0, env1) {
return (JSON.stringify(env0) === JSON.stringify(env1)); return (JSON.stringify(env0) === JSON.stringify(env1));
} }
@ -713,8 +571,8 @@ RED.editor = (function() {
$(this).attr("data-i18n",keys.join(";")); $(this).attr("data-i18n",keys.join(";"));
}); });
if ((type === "subflow") || (type === "subflow-template")) { if (type === "subflow-template" || type === "subflow") {
buildEnvForm(dialogForm, node); RED.subflow.buildEditForm(dialogForm,type,node);
} }
// Add dummy fields to prevent 'Enter' submitting the form in some // Add dummy fields to prevent 'Enter' submitting the form in some
@ -857,7 +715,7 @@ RED.editor = (function() {
} }
return result; return result;
} }
function showIconPicker(container, node, iconPath, done) { function showIconPicker(container, node, iconPath, faOnly, done) {
var containerPos = container.offset(); var containerPos = container.offset();
var pickerBackground = $('<div>').css({ var pickerBackground = $('<div>').css({
position: "absolute",top:0,bottom:0,left:0,right:0,zIndex:20 position: "absolute",top:0,bottom:0,left:0,right:0,zIndex:20
@ -912,7 +770,14 @@ RED.editor = (function() {
done(null); done(null);
}); });
var iconSets = RED.nodes.getIconSets(); var iconSets = RED.nodes.getIconSets();
var backgroundColor = node && RED.utils.getNodeColor(node.type, node._def);
if (!node && faOnly) {
iconList.addClass("red-ui-icon-list-dark");
}
Object.keys(iconSets).forEach(function(moduleName) { Object.keys(iconSets).forEach(function(moduleName) {
if (faOnly && (moduleName !== "font-awesome")) {
return;
}
var icons = iconSets[moduleName]; var icons = iconSets[moduleName];
if (icons.length > 0) { if (icons.length > 0) {
// selectIconModule.append($("<option></option>").val(moduleName).text(moduleName)); // selectIconModule.append($("<option></option>").val(moduleName).text(moduleName));
@ -921,10 +786,13 @@ RED.editor = (function() {
icons.forEach(function(icon) { icons.forEach(function(icon) {
var iconDiv = $('<div>',{class:"red-ui-icon-list-icon"}).appendTo(iconList); var iconDiv = $('<div>',{class:"red-ui-icon-list-icon"}).appendTo(iconList);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconDiv); var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconDiv);
var colour = RED.utils.getNodeColor(node.type, node._def);
var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon; var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon;
iconDiv.data('icon',icon_url); iconDiv.data('icon',icon_url);
nodeDiv.css('backgroundColor',colour); if (node) {
nodeDiv.css({
'backgroundColor': backgroundColor
});
}
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, true); RED.utils.createIconElement(icon_url, iconContainer, true);
@ -1067,8 +935,65 @@ RED.editor = (function() {
var i,row; var i,row;
if (node.type === "subflow") {
var categoryRow = $("<div/>", {
class: "form-row"
}).appendTo(dialogForm);
$("<label/>", {
for: "subflow-appearance-input-category",
"data-i18n": "editor:subflow.category"
}).appendTo(categoryRow);
var categorySelector = $("<select/>", {
id: "subflow-appearance-input-category"
}).css({
width: "250px"
}).appendTo(categoryRow);
$("<input/>", {
type: "text",
id: "subflow-appearance-input-custom-category"
}).css({
display: "none",
"margin-left": "10px",
width: "calc(100% - 250px)"
}).appendTo(categoryRow);
var categories = RED.palette.getCategories();
categories.sort(function(A,B) {
return A.label.localeCompare(B.label);
})
categories.forEach(function(cat) {
categorySelector.append($("<option/>").val(cat.id).text(cat.label));
})
categorySelector.append($("<option/>").attr('disabled',true).text("---"));
categorySelector.append($("<option/>").val("_custom_").text(RED._("palette.addCategory")));
$("#subflow-appearance-input-category").on("change", function() {
var val = $(this).val();
if (val === "_custom_") {
$("#subflow-appearance-input-category").width(120);
$("#subflow-appearance-input-custom-category").show();
} else {
$("#subflow-appearance-input-category").width(250);
$("#subflow-appearance-input-custom-category").hide();
}
})
$("#subflow-appearance-input-category").val(node.category||"subflows");
var userCount = 0;
var subflowType = "subflow:"+node.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
userCount++;
}
});
$("#red-ui-editor-subflow-user-count")
.text(RED._("subflow.subflowInstances", {count:userCount})).show();
}
$('<div class="form-row">'+ $('<div class="form-row">'+
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+ '<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
'<span style="margin-right: 2px;"/>'+
'<input type="checkbox" id="node-input-show-label"/>'+ '<input type="checkbox" id="node-input-show-label"/>'+
'</div>').appendTo(dialogForm); '</div>').appendTo(dialogForm);
@ -1106,7 +1031,7 @@ RED.editor = (function() {
} else { } else {
iconPath = RED.utils.getDefaultNodeIcon(node._def, node); iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
} }
showIconPicker(iconRow,node,iconPath,function(newIcon) { showIconPicker(iconRow,node,iconPath,false,function(newIcon) {
$("#red-ui-editor-node-icon").text(newIcon||""); $("#red-ui-editor-node-icon").text(newIcon||"");
var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon}); var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon});
RED.utils.createIconElement(icon_url, iconContainer, true); RED.utils.createIconElement(icon_url, iconContainer, true);
@ -1160,6 +1085,7 @@ RED.editor = (function() {
} }
} }
function updateLabels(editing_node, changes, outputMap) { function updateLabels(editing_node, changes, outputMap) {
var inputLabels = $("#red-ui-editor-node-label-form-inputs").children().find("input"); var inputLabels = $("#red-ui-editor-node-label-form-inputs").children().find("input");
var outputLabels = $("#red-ui-editor-node-label-form-outputs").children().find("input"); var outputLabels = $("#red-ui-editor-node-label-form-outputs").children().find("input");
@ -1550,13 +1476,13 @@ RED.editor = (function() {
if (type === "subflow") { if (type === "subflow") {
var old_env = editing_node.env; var old_env = editing_node.env;
var new_env = exportEnvList($("#node-input-env-container").editableList("items")); var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node);
if (!isSameEnv(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env; editing_node.env = new_env;
changes.env = editing_node.env; changes.env = editing_node.env;
changed = true; changed = true;
} }
} }
if (changed) { if (changed) {
var wasChanged = editing_node.changed; var wasChanged = editing_node.changed;
@ -1674,6 +1600,19 @@ RED.editor = (function() {
buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns,node); buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns,node);
editorTabs.addTab(nodePropertiesTab); editorTabs.addTab(nodePropertiesTab);
if (/^subflow:/.test(node.type)) {
var subflowPropertiesTab = {
id: "editor-subflow-envProperties",
label: RED._("editor-tab.envProperties"),
name: RED._("editor-tab.envProperties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-list"
};
RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node);
editorTabs.addTab(subflowPropertiesTab);
}
if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) {
var descriptionTab = { var descriptionTab = {
id: "editor-tab-description", id: "editor-tab-description",
@ -1942,7 +1881,6 @@ RED.editor = (function() {
var configId = editing_config_node.id; var configId = editing_config_node.id;
var configAdding = adding; var configAdding = adding;
var configTypeDef = RED.nodes.getType(configType); var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.oneditcancel) { if (configTypeDef.oneditcancel) {
// TODO: what to pass as this to call // TODO: what to pass as this to call
if (configTypeDef.oneditcancel) { if (configTypeDef.oneditcancel) {
@ -2286,9 +2224,9 @@ RED.editor = (function() {
editing_node.icon = icon; editing_node.icon = icon;
changed = true; changed = true;
} }
var newCategory = $("#subflow-input-category").val().trim(); var newCategory = $("#subflow-appearance-input-category").val().trim();
if (newCategory === "_custom_") { if (newCategory === "_custom_") {
newCategory = $("#subflow-input-custom-category").val().trim(); newCategory = $("#subflow-appearance-input-custom-category").val().trim();
if (newCategory === "") { if (newCategory === "") {
newCategory = editing_node.category; newCategory = editing_node.category;
} }
@ -2312,8 +2250,8 @@ RED.editor = (function() {
} }
var old_env = editing_node.env; var old_env = editing_node.env;
var new_env = exportEnvList($("#node-input-env-container").editableList("items")); var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items"));
if (!isSameEnv(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env; editing_node.env = new_env;
changes.env = editing_node.env; changes.env = editing_node.env;
changed = true; changed = true;
@ -2359,21 +2297,39 @@ RED.editor = (function() {
], ],
resize: function(size) { resize: function(size) {
$(".red-ui-tray-content").height(size.height - 50); $(".red-ui-tray-content").height(size.height - 50);
// var form = $(".red-ui-tray-content form").height(size.height - 50 - 40); var envContainer = $("#node-input-env-container");
var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); if (envContainer.length) {
var height = size.height; // var form = $(".red-ui-tray-content form").height(size.height - 50 - 40);
for (var i=0; i<rows.size(); i++) { var rows = $("#dialog-form>div:not(#subflow-env-tabs-content)");
height -= $(rows[i]).outerHeight(true); var height = size.height;
for (var i=0; i<rows.size(); i++) {
height -= $(rows[i]).outerHeight(true);
}
// var editorRow = $("#dialog-form>div.node-input-env-container-row");
// height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-env-container").editableList('height',height-95);
} }
var editorRow = $("#dialog-form>div.node-input-env-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-env-container").editableList('height',height-80);
}, },
open: function(tray) { open: function(tray) {
var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left"
}).appendTo(trayFooter)
var trayBody = tray.find('.red-ui-tray-body'); var trayBody = tray.find('.red-ui-tray-body');
trayBody.parent().css('overflow','hidden'); trayBody.parent().css('overflow','hidden');
if (editing_node.type === "subflow") {
var span = $("<span/>").css({
"margin-left": "10px"
}).appendTo(trayFooterLeft);
$("<i/>", {
class: "fa fa-info-circle"
}).appendTo(span);
$("<span/>").text(" ").appendTo(span);
$("<i/>", {
id: "red-ui-editor-subflow-user-count"
}).appendTo(span);
}
if (editing_node) { if (editing_node) {
RED.sidebar.info.refresh(editing_node); RED.sidebar.info.refresh(editing_node);
@ -2433,46 +2389,9 @@ RED.editor = (function() {
buildAppearanceForm(appearanceTab.content,editing_node); buildAppearanceForm(appearanceTab.content,editing_node);
editorTabs.addTab(appearanceTab); editorTabs.addTab(appearanceTab);
$("#subflow-input-name").val(subflow.name); $("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name")); RED.text.bidi.prepareInput($("#subflow-input-name"));
$("#subflow-input-category").empty();
var categories = RED.palette.getCategories();
categories.sort(function(A,B) {
return A.label.localeCompare(B.label);
})
categories.forEach(function(cat) {
$("#subflow-input-category").append($("<option></option>").val(cat.id).text(cat.label));
})
$("#subflow-input-category").append($("<option></option>").attr('disabled',true).text("---"));
$("#subflow-input-category").append($("<option></option>").val("_custom_").text(RED._("palette.addCategory")));
$("#subflow-input-category").on("change", function() {
var val = $(this).val();
if (val === "_custom_") {
$("#subflow-input-category").width(120);
$("#subflow-input-custom-category").show();
} else {
$("#subflow-input-category").width(250);
$("#subflow-input-custom-category").hide();
}
})
$("#subflow-input-category").val(subflow.category||"subflows");
var userCount = 0;
var subflowType = "subflow:"+editing_node.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
userCount++;
}
});
$("#subflow-dialog-user-count").text(RED._("subflow.subflowInstances", {count:userCount})).show();
trayBody.i18n(); trayBody.i18n();
finishedBuilding = true; finishedBuilding = true;
}, },
@ -2625,6 +2544,8 @@ RED.editor = (function() {
validateNode: validateNode, validateNode: validateNode,
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
showIconPicker:showIconPicker,
/** /**
* Show a type editor. * Show a type editor.
* @param {string} type - the type to display * @param {string} type - the type to display

View File

@ -152,7 +152,7 @@ RED.palette = (function() {
function getPaletteNode(type) { function getPaletteNode(type) {
return $(".red-ui-palette-node[data-palette-type='"+type+"']"); return $(".red-ui-palette-node[data-palette-type='"+type+"']");
} }
function addNodeType(nt,def) { function addNodeType(nt,def) {
if (getPaletteNode(nt).length) { if (getPaletteNode(nt).length) {
return; return;
@ -241,7 +241,6 @@ RED.palette = (function() {
RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp")); RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp"));
}); });
var chart = $("#red-ui-workspace-chart"); var chart = $("#red-ui-workspace-chart");
var chartOffset = chart.offset();
var chartSVG = $("#red-ui-workspace-chart>svg").get(0); var chartSVG = $("#red-ui-workspace-chart>svg").get(0);
var activeSpliceLink; var activeSpliceLink;
var mouseX; var mouseX;
@ -265,8 +264,8 @@ RED.palette = (function() {
ui.originalPosition.left = $('#' + e.target.id).offset().left; ui.originalPosition.left = $('#' + e.target.id).offset().left;
if (def.inputs > 0 && def.outputs > 0) { if (def.inputs > 0 && def.outputs > 0) {
mouseX = ui.position.left-paletteWidth+(ui.helper.width()/2) - chartOffset.left + chart.scrollLeft(); mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
mouseY = ui.position.top-paletteTop+(ui.helper.height()/2) - chartOffset.top + chart.scrollTop(); mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop();
if (!spliceTimer) { if (!spliceTimer) {
spliceTimer = setTimeout(function() { spliceTimer = setTimeout(function() {
var nodes = []; var nodes = [];
@ -416,7 +415,7 @@ RED.palette = (function() {
createCategory(newCategory,category,category,"node-red"); createCategory(newCategory,category,category,"node-red");
var currentCategoryNode = paletteNode.closest(".red-ui-palette-category"); var currentCategoryNode = paletteNode.closest(".red-ui-palette-category");
var newCategoryNode = $("#palette-"+category); var newCategoryNode = $("#red-ui-palette-"+category);
newCategoryNode.append(paletteNode); newCategoryNode.append(paletteNode);
if (newCategoryNode.find(".red-ui-palette-node").length === 1) { if (newCategoryNode.find(".red-ui-palette-node").length === 1) {
categoryContainers[category].open(); categoryContainers[category].open();

File diff suppressed because it is too large Load Diff

View File

@ -406,6 +406,7 @@ RED.view = (function() {
RED.actions.add("core:delete-selection",deleteSelection); RED.actions.add("core:delete-selection",deleteSelection);
RED.actions.add("core:edit-selected-node",editSelection); RED.actions.add("core:edit-selected-node",editSelection);
RED.actions.add("core:undo",RED.history.pop); RED.actions.add("core:undo",RED.history.pop);
RED.actions.add("core:redo",RED.history.redo);
RED.actions.add("core:select-all-nodes",selectAll); RED.actions.add("core:select-all-nodes",selectAll);
RED.actions.add("core:zoom-in",zoomIn); RED.actions.add("core:zoom-in",zoomIn);
RED.actions.add("core:zoom-out",zoomOut); RED.actions.add("core:zoom-out",zoomOut);
@ -3166,7 +3167,7 @@ RED.view = (function() {
var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill;
thisNode.selectAll(".red-ui-flow-node-status").style("display","inline").attr("class","red-ui-flow-node-status "+statusClass); thisNode.selectAll(".red-ui-flow-node-status").style("display","inline").attr("class","red-ui-flow-node-status "+statusClass);
} }
if (d.status.text) { if (d.status.hasOwnProperty('text')) {
thisNode.selectAll(".red-ui-flow-node-status-label").text(d.status.text); thisNode.selectAll(".red-ui-flow-node-status-label").text(d.status.text);
} else { } else {
thisNode.selectAll(".red-ui-flow-node-status-label").text(""); thisNode.selectAll(".red-ui-flow-node-status-label").text("");

View File

@ -60,10 +60,12 @@
.red-ui-icon-picker { .red-ui-icon-picker {
a { a {
text-decoration: none; text-decoration: none;
color: $primary-text-color;
} }
a:hover, a:hover,
a:focus { a:focus {
text-decoration: none; text-decoration: none;
color: $primary-text-color;
} }
p { p {

View File

@ -79,6 +79,9 @@
} }
a { a {
img {
max-width: 14px;
}
.fa { .fa {
width: 20px; width: 20px;
margin-left: -25px; margin-left: -25px;

View File

@ -190,6 +190,10 @@ button.red-ui-tray-resize-button {
border-color: $form-input-border-error-color !important; border-color: $form-input-border-error-color !important;
} }
.input-updated {
border-color: $node-selected-color !important;
}
.form-row { .form-row {
clear: both; clear: both;
color: $form-text-color; color: $form-text-color;
@ -421,6 +425,15 @@ button.red-ui-button-small
height: 200px; height: 200px;
overflow-y: scroll; overflow-y: scroll;
line-height: 0px; line-height: 0px;
&.red-ui-icon-list-dark {
.red-ui-palette-icon-fa {
color: $secondary-text-color;
}
.red-ui-palette-icon-container {
background: $secondary-background;
border-radius: 4px;
}
}
} }
.red-ui-icon-list-icon { .red-ui-icon-list-icon {
display: inline-block; display: inline-block;
@ -428,6 +441,7 @@ button.red-ui-button-small
padding: 4px; padding: 4px;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
&:hover { &:hover {
background: $list-item-background-hover; background: $list-item-background-hover;
} }
@ -599,3 +613,405 @@ button.red-ui-toggleButton.toggle {
border: solid 1px $primary-border-color; border: solid 1px $primary-border-color;
border-radius: 2px; border-radius: 2px;
} }
.red-ui-editor-subflow-env-ui-row {
margin-right: 3px;
>div {
display: grid;
grid-template-columns: 16px 40px 35% auto;
}
>div:first-child {
font-size: 0.9em;
color: $tertiary-text-color;
margin: 3px 0 -4px;
>div {
padding-left: 3px;
}
}
>div:last-child {
>div {
height: 40px;
line-height: 30px;
display: inline-block;
box-sizing: border-box;
// border-left: 2px dashed $secondary-border-color;
// border-bottom: 2px dashed $secondary-border-color;
// border: 1px dashed $secondary-border-color;
border-right: none;
&:not(:first-child) {
padding: 3px;
}
// &:last-child {
// border-right: 1px dashed $secondary-border-color;
// }
.placeholder-input {
position: relative;
padding: 0 3px;
line-height: 24px;
opacity: 0.8
}
.red-ui-typedInput-value-label,.red-ui-typedInput-option-label {
select,.placeholder-input {
margin: 3px;
height: 26px;
width: calc(100% - 10px);
padding-left: 3px;
}
.placeholder-input {
span:first-child {
display:inline-block;
height: 100%;
width: 20px;
text-align:center;
border-right: 1px solid $secondary-border-color;
background: $tertiary-background;
}
}
input[type="checkbox"] {
margin-left: 8px;
margin-top: 0;
height: 100%;
}
}
}
>div:nth-child(1) {
border: none;
padding: 2px;
.red-ui-editableList-item-handle {
position:relative;
top: 0px;
color: $tertiary-text-color;
}
}
>div:nth-child(2) {
margin: 4px;
height: 32px;
border: 1px dashed $secondary-border-color;
text-align: center;
a {
display: block;
width: 100%;
height: 100%;
line-height: 32px;
&:hover {
background: $secondary-background-hover;
}
i {
height: 100%;
vertical-align: middle;
}
}
}
>div:nth-child(3) {
position: relative;
input {
width: 100%;
}
}
}
}
span.red-ui-editor-subflow-env-lang-icon {
position: absolute;
display: inline-block;
background: $secondary-background;
opacity: 0.8;
width: 20px;
line-height: 32px;
height: 32px;
text-align: center;
top: 4px;
right: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
// .red-ui-editor-subflow-ui-grid {
// width: 100%;
// .red-ui-editableList-container {
// border: none;
// border-radius: 0;
// }
// .red-ui-editableList-container li {
// border: none;
// padding: 0;
// &:not(:first-child) .red-ui-editableList-item-content >div:first-child >div {
// border-top: none;
// }
// &.ui-sortable-helper {
// border: 2px dashed $secondary-border-color;
// .red-ui-editableList-item-content {
// >div {
// border: none;
// opacity: 0.7
// }
//
// }
// }
// }
//
// .red-ui-editableList-item-content {
// >div>div {
// display: inline-block;
// box-sizing: border-box;
// border-left: 1px dashed $secondary-border-color;
// border-bottom: 1px dashed $secondary-border-color;
// }
// >div:first-child {
// font-size: 0.9em;
// display: grid;
// grid-template-columns: 25px auto 20px;
// >div {
// border-top: 1px dashed $secondary-border-color;
// padding: 1px;
// }
// >div:nth-child(3) {
// border-top: none;
// border-bottom: none;
// // width: 20px;
// }
// }
// >div:last-child {
// display: grid;
// grid-template-columns: 25px 140px auto 20px;
// >div {
// height: 48px;
// line-height: 30px;
// // display: inline-block;
// // height: 48px;
// // line-height: 30px;
// // box-sizing: border-box;
// //
// // border-left: 2px dashed $secondary-border-color;
// border-top: none;
// // border-bottom: 2px dashed $secondary-border-color;
// &:not(:first-child) {
// padding: 6px 3px;
// }
// .placeholder-input {
// position: relative;
// padding: 0 3px;
// line-height: 24px;
// opacity: 0.8
// }
// .red-ui-typedInput-value-label,.red-ui-typedInput-option-label {
// select,.placeholder-input {
// margin: 3px;
// height: 26px;
// width: calc(100% - 10px);
// padding-left: 3px;
// }
// input[type="checkbox"] {
// margin-left: 8px;
// margin-top: 0;
// height: 100%;
// }
// }
// }
// >div:nth-child(1) {
// text-align: center;
// a {
// display: block;
// width: 100%;
// height: 100%;
// line-height: 45px;
// &:hover {
// background: $secondary-background-hover;
// }
// }
// }
// >div:nth-child(2) {
// input {
// width: 100%;
// }
// // width: 140px;
// }
// >div:nth-child(3) {
// position: relative;
// .options-button {
// position: absolute;
// top: calc(50% - 10px);
// margin-right: 2px;
// right: 2px;
// }
// }
// >div:nth-child(4) {
// border-top: none;
// border-bottom: none;
// // width: 20px;
// }
//
// }
// }
// }
.red-ui-editor-subflow-ui-edit-panel {
padding-bottom: 3px;
background: $primary-background;
.red-ui-editableList-border {
border: none;
border-radius: 0;
border-bottom: 1px solid $secondary-border-color;
}
.red-ui-editableList-container {
}
.red-ui-editableList-addButton {
margin-left: 2px;
}
.red-ui-editableList-header {
background: $primary-background;
display: grid;
grid-template-columns: 50% 50%;
color: $secondary-text-color;
div:first-child {
padding-left: 23px;
}
div:last-child {
padding-left: 3px;
}
}
.red-ui-editableList-container {
padding: 0 1px;
li {
background: $secondary-background;
// border-bottom: none;
padding: 0;
.red-ui-editableList-item-content {
display: grid;
grid-template-columns: 50% 50%;
>div {
position:relative;
}
}
input {
margin-bottom: 0;
border:none;
width: 100%;
border-right: 1px solid $secondary-border-color;
border-radius: 0;
&:focus {
box-shadow: 0 0 0 1px inset $form-input-focus-color;
}
&:first-child {
border-left: 1px solid $secondary-border-color;
}
}
button.red-ui-typedInput-type-select, button.red-ui-typedInput-option-expand, button.red-ui-typedInput-option-trigger {
border-radius: 0;
height: 34px;
}
.red-ui-typedInput-container {
border-radius: 0;
border: none;
input.red-ui-typedInput-input {
height: 34px;
border-right: none;
}
}
.red-ui-editor-subflow-env-lang-icon {
top: 1px;
right: 1px;
border-top-right-radius:0;
border-bottom-right-radius:0;
}
.red-ui-editableList-item-remove {
right: 3px;
}
}
}
}
.node-input-env-locales-row {
position: relative;
top: -20px;
float: right;
select {
width: 160px;
height: 20px;
min-width: 20px;
line-height: 18px;
font-size: 10px;
}
}
.node-input-env-container-row {
min-width: 470px;
position: relative;
.red-ui-editableList-item-content {
label {
margin-bottom: 0;
line-height: 32px;
span {
display: inline-block;
width: 20px;
text-align: center;
}
}
>div:first-child {
display: grid;
padding-left: 5px;
grid-template-columns: 40% auto 37px;
> :first-child {
width: calc(100% - 5px);
}
input {
width: calc(100% - 5px);
}
}
&.red-ui-editor-subflow-env-editable {
>div:first-child {
padding-left: 0;
grid-template-columns: 24px 40% auto 37px;
> a:first-child {
text-align: center;
line-height: 32px;
i.fa-angle-right {
transition: all 0.2s linear;
}
&.expanded {
i.fa-angle-right {
transform: rotate(90deg);
}
}
}
}
}
}
.red-ui-editableList-border .red-ui-editableList-header {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background: $tertiary-background;
padding: 0;
>div {
display: grid;
grid-template-columns: 24px 40% auto 37px;
>div {
display: inline-block;
}
}
}
.red-ui-editableList-container {
padding: 0;
.red-ui-editableList-item-handle {
top: 25px;
}
.red-ui-editableList-item-remove {
top: 25px;
right: 5px;
}
}
}
#subflow-input-ui {
// .form-row {
// display: grid;
// grid-template-columns: 120px auto;
// label span {
// display: inline-block;
// width: 20px;
// text-align: center;
// }
// }
}

View File

@ -150,7 +150,8 @@
input[type="tel"], input[type="tel"],
input[type="color"], input[type="color"],
div[contenteditable="true"], div[contenteditable="true"],
.uneditable-input { .uneditable-input,
.placeholder-input {
box-sizing: border-box; box-sizing: border-box;
display: inline-block; display: inline-block;
height: 34px; height: 34px;
@ -190,7 +191,8 @@
input[type="tel"], input[type="tel"],
input[type="color"], input[type="color"],
div[contenteditable="true"], div[contenteditable="true"],
.uneditable-input { .uneditable-input,
.placeholder-input {
background-color: $form-input-background; background-color: $form-input-background;
border: 1px solid $form-input-border-color; border: 1px solid $form-input-border-color;
} }

View File

@ -192,6 +192,7 @@
color: $header-menu-color; color: $header-menu-color;
padding: 3px 40px; padding: 3px 40px;
img { img {
max-width: 100%;
margin-right: 10px; margin-right: 10px;
padding: 4px; padding: 4px;
border: 3px solid transparent; border: 3px solid transparent;

View File

@ -162,3 +162,15 @@
background: none; background: none;
color: $tertiary-text-color; color: $tertiary-text-color;
} }
.red-ui-popover-panel {
@include component-shadow;
font-family: $primary-font;
font-size: $primary-font-size;
position: absolute;
box-sizing: border-box;
border: 1px solid $primary-border-color;
background: $secondary-background;
z-index: 2000;
}

View File

@ -58,118 +58,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
button.red-ui-typedInput-type-select,
button.red-ui-typedInput-option-expand,
button.red-ui-typedInput-option-trigger
{
text-align: left;
border: none;
position: absolute;
box-sizing: border-box;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
padding: 0 1px 0 5px;
display:inline-block;
background: $form-button-background;
height: 32px;
line-height: 32px;
vertical-align: middle;
color: $form-text-color;
i.red-ui-typedInput-icon {
margin-left: 1px;
margin-right: 2px;
vertical-align: middle;
}
&.disabled {
cursor: default;
i.red-ui-typedInput-icon {
color: $secondary-text-color-disabled;
}
}
.red-ui-typedInput-type-label,.red-ui-typedInput-option-label {
display: inline-block;
height: 100%;
padding: 0 1px 0 5px;
img {
max-width: none;
}
}
&:not(.disabled):hover {
text-decoration: none;
background: $workspace-button-background-hover;
}
&:focus {
text-decoration: none;
outline: none;
box-shadow: inset 0 0 0 1px $form-input-focus-color;
}
&:not(.disabled):active {
background: $workspace-button-background-active;
text-decoration: none;
}
&.red-ui-typedInput-full-width {
width: 100%;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
&:before {
content:'';
display: inline-block;
height: 100%;
vertical-align: middle;
}
}
button.red-ui-typedInput-option-expand {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
right: 0;
}
button.red-ui-typedInput-option-trigger {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
padding: 0 0 0 0;
position:absolute;
right: 0;
.red-ui-typedInput-option-label {
background:$form-button-background;
color: $form-text-color;
position:absolute;
left:0;
right:23px;
top: 0;
padding: 0 5px 0 8px;
i.red-ui-typedInput-icon {
margin-right: 4px;
}
}
.red-ui-typedInput-option-caret {
top: 0;
position: absolute;
right: 0;
bottom: 0;
width: 17px;
padding-left: 6px;
&:before {
content:'';
display: inline-block;
height: 100%;
vertical-align: middle;
}
}
&:focus {
box-shadow: none;
}
&:focus .red-ui-typedInput-option-caret {
box-shadow: inset 0 0 0 1px $form-input-focus-color;
}
}
} }
.red-ui-typedInput-options { .red-ui-typedInput-options {
@include component-shadow; @include component-shadow;
@ -180,6 +68,7 @@
max-height: 350px; max-height: 350px;
overflow-y: auto; overflow-y: auto;
border: 1px solid $primary-border-color; border: 1px solid $primary-border-color;
box-sizing: border-box;
background: $secondary-background; background: $secondary-background;
z-index: 2000; z-index: 2000;
a { a {
@ -200,8 +89,124 @@
text-decoration: none; text-decoration: none;
background: $workspace-button-background-active; background: $workspace-button-background-active;
} }
input[type="checkbox"] {
margin-right: 6px;
}
} }
.red-ui-typedInput-icon { .red-ui-typedInput-icon {
margin-right: 4px; margin-right: 6px;
}
}
button.red-ui-typedInput-type-select,
button.red-ui-typedInput-option-expand,
button.red-ui-typedInput-option-trigger
{
text-align: left;
border: none;
position: absolute;
box-sizing: border-box;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
padding: 0 1px 0 5px;
display:inline-block;
background: $form-button-background;
height: 32px;
line-height: 30px;
min-width: 23px;
vertical-align: middle;
color: $form-text-color;
i.red-ui-typedInput-icon {
margin-left: 1px;
margin-right: 2px;
vertical-align: middle;
}
&.disabled {
cursor: default;
i.red-ui-typedInput-icon {
color: $secondary-text-color-disabled;
}
}
.red-ui-typedInput-type-label,.red-ui-typedInput-option-label {
display: inline-block;
vertical-align: middle;
height: 100%;
padding: 0 1px 0 5px;
img {
max-width: none;
}
}
&:not(.disabled):hover {
text-decoration: none;
background: $workspace-button-background-hover;
}
&:focus {
text-decoration: none;
outline: none;
box-shadow: inset 0 0 0 1px $form-input-focus-color;
}
&:not(.disabled):active {
background: $workspace-button-background-active;
text-decoration: none;
}
&.red-ui-typedInput-full-width {
width: 100%;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
&:before {
content:'';
display: inline-block;
height: 100%;
vertical-align: middle;
}
}
button.red-ui-typedInput-option-expand {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
right: 0;
}
button.red-ui-typedInput-option-trigger {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
padding: 0 0 0 0;
position:absolute;
right: 0;
.red-ui-typedInput-option-label {
background:$form-button-background;
color: $form-text-color;
position:absolute;
left:0;
right:23px;
top: 0;
padding: 0 5px 0 8px;
i.red-ui-typedInput-icon {
margin-right: 4px;
}
}
.red-ui-typedInput-option-caret {
top: 0;
position: absolute;
right: 0;
bottom: 0;
width: 17px;
padding-left: 5px;
&:before {
content:'';
display: inline-block;
height: 100%;
vertical-align: middle;
}
}
&:focus {
box-shadow: none;
}
&:focus .red-ui-typedInput-option-caret {
box-shadow: inset 0 0 0 1px $form-input-focus-color;
} }
} }

View File

@ -42,14 +42,14 @@ RED.debug = (function() {
var content = $("<div>").css({"position":"relative","height":"100%"}); var content = $("<div>").css({"position":"relative","height":"100%"});
var toolbar = $('<div class="red-ui-sidebar-header">'+ var toolbar = $('<div class="red-ui-sidebar-header">'+
'<span class="button-group"><a id="red-ui-sidebar-debug-filter" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span></a></span>'+ '<span class="button-group"><a id="red-ui-sidebar-debug-filter" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span></a></span>'+
'<span class="button-group"><a id="red-ui-sidebar-debug-clear" class="red-ui-sidebar-header-button" href="#" data-i18n="[title]node-red:debug.sidebar.clearLog"><i class="fa fa-trash"></i></a></span></div>').appendTo(content); '<span class="button-group"><a id="red-ui-sidebar-debug-clear" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-trash"></i></a></span></div>').appendTo(content);
var footerToolbar = $('<div>'+ var footerToolbar = $('<div>'+
// '<span class="button-group">'+ // '<span class="button-group">'+
// '<a class="red-ui-footer-button-toggle text-button selected" id="red-ui-sidebar-debug-view-list" href="#"><span data-i18n="">list</span></a>'+ // '<a class="red-ui-footer-button-toggle text-button selected" id="red-ui-sidebar-debug-view-list" href="#"><span data-i18n="">list</span></a>'+
// '<a class="red-ui-footer-button-toggle text-button" id="red-ui-sidebar-debug-view-table" href="#"><span data-i18n="">table</span></a> '+ // '<a class="red-ui-footer-button-toggle text-button" id="red-ui-sidebar-debug-view-table" href="#"><span data-i18n="">table</span></a> '+
// '</span>'+ // '</span>'+
'<span class="button-group"><a id="red-ui-sidebar-debug-open" class="red-ui-footer-button" href="#" data-i18n="[title]node-red:debug.sidebar.openWindow"><i class="fa fa-desktop"></i></a></span> ' + '<span class="button-group"><a id="red-ui-sidebar-debug-open" class="red-ui-footer-button" href="#"><i class="fa fa-desktop"></i></a></span> ' +
'</div>'); '</div>');
messageList = $('<div class="red-ui-debug-content red-ui-debug-content-list"/>').appendTo(content); messageList = $('<div class="red-ui-debug-content red-ui-debug-content-list"/>').appendTo(content);

View File

@ -1,544 +0,0 @@
<style>
.rpi-gpio-pinTable {
width: 340px;
display: inline-table;
font-size: 13px;
height: 380px;
min-height: 380px;
max-height: 380px;
}
.rpi-gpio-pinTable input[type="radio"] {
width: auto;
margin: 2px 2px;
}
.rpi-gpio-pinTable label {
width: auto;
margin: 0;
display: block;
}
.rpi-gpio-pinTable .pinTableBody {
width: 340px;
display: table-row-group;
line-height: 12px;
}
.rpi-gpio-pinTable .pinTableRow {
width: 340px;
display: table-row;
height: 14px;
}
.rpi-gpio-pinTable .pinTableCellL {
width: 170px;
display: table-cell;
text-align: right;
padding-right: 4px;
vertical-align: top;
border: 1px solid #444;
}
.rpi-gpio-pinTable .pinTableCellR {
width: 170px;
display: table-cell;
text-align: left;
padding-left: 4px;
vertical-align: top;
border: 1px solid #000;
}
.rpi-gpio-pinTable .pinColorPower {
background-color:#FECBCE;
}
.rpi-gpio-pinTable .pinColorGround {
background-color:#DDDDDD;
}
.rpi-gpio-pinTable .pinColorGPIO {
background-color:#BFEBBF;
}
.rpi-gpio-pinTable .pinColorDual {
background-color:#D0E6F4;
}
.rpi-gpio-pinTable .pinColorSD {
background-color:#FFFDD0;
}
</style>
<script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row" style="min-width: 540px">
<label><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.pinname"></span></label>
<input type="text" id="node-input-pin" style="display:none;">
<div class="rpi-gpio-pinTable">
<div class="pinTableBody" id="pinform">
<div class="pinTableRow">
<div class="pinTableCellL pinColorPower"><label>3.3V Power - 1 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorPower"><label><input disabled type="radio" name="pins" value=""> 2 - 5V Power</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-3">SDA1 - GPIO02 - 3 <input id="pinTable-pin-3" type="radio" name="pins" value="3"></label></div>
<div class="pinTableCellR pinColorPower"><label><input disabled type="radio" name="pins" value=""> 4 - 5V Power</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-5">SCL1 - GPIO03 - 5 <input id="pinTable-pin-5" type="radio" name="pins" value="5"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 6 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-7">GPIO04 - 7 <input id="pinTable-pin-7" type="radio" name="pins" value="7"></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-8"><input id="pinTable-pin-8" type="radio" name="pins" value="8"> 8 - GPIO14 - TxD</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGround"><label>Ground - 9 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-10"><input id="pinTable-pin-10" type="radio" name="pins" value="10"> 10 - GPIO15 - RxD</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-11">GPIO17 - 11 <input id="pinTable-pin-11" type="radio" name="pins" value="11"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-12"><input id="pinTable-pin-12" type="radio" name="pins" value="12"> 12 - GPIO18</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-13">GPIO27 - 13 <input id="pinTable-pin-13" type="radio" name="pins" value="13"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 14 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-15">GPIO22 - 15 <input id="pinTable-pin-15" type="radio" name="pins" value="15"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-16"><input id="pinTable-pin-16" type="radio" name="pins" value="16"> 16 - GPIO23</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorPower"><label>3.3V Power - 17 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-18"><input id="pinTable-pin-18" type="radio" name="pins" value="18"> 18 - GPIO24</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-19">MOSI - GPIO10 - 19 <input id="pinTable-pin-19" type="radio" name="pins" value="19"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 20 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-21">MISO - GPIO09 - 21 <input id="pinTable-pin-21" type="radio" name="pins" value="21"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-22"><input id="pinTable-pin-22" type="radio" name="pins" value="22"> 22 - GPIO25</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-23">SCLK - GPIO11 - 23 <input id="pinTable-pin-23" type="radio" name="pins" value="23"></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-24"><input id="pinTable-pin-24" type="radio" name="pins" value="24"> 24 - GPIO8 - CE0</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGround"><label>Ground - 25 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-26"><input id="pinTable-pin-26" type="radio" name="pins" value="26"> 26 - GPIO7 - CE1</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorSD"><label>SD - 27 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorSD"><label><input disabled type="radio" name="pins" value=""> 28 - SC</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-29">GPIO05 - 29 <input id="pinTable-pin-29" type="radio" name="pins" value="29"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 30 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-31">GPIO06 - 31 <input id="pinTable-pin-31" type="radio" name="pins" value="31"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-32"><input id="pinTable-pin-32" type="radio" name="pins" value="32"> 32 - GPIO12</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-33">GPIO13 - 33 <input id="pinTable-pin-33" type="radio" name="pins" value="33"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 34 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-35">GPIO19 - 35 <input id="pinTable-pin-35" type="radio" name="pins" value="35"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-36"><input id="pinTable-pin-36" type="radio" name="pins" value="36"> 36 - GPIO16</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-37">GPIO26 - 37 <input id="pinTable-pin-37" type="radio" name="pins" value="37"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-38"><input id="pinTable-pin-38" type="radio" name="pins" value="38"> 38 - GPIO20</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGround"><label>Ground - 39 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-40"><input id="pinTable-pin-40" type="radio" name="pins" value="40"> 40 - GPIO21</label></div>
</div>
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-intype"><i class="fa fa-level-up"></i> <span data-i18n="rpi-gpio.label.resistor"></span></label>
<select type="text" id="node-input-intype" style="width:100px;">
<option value="tri" data-i18n="rpi-gpio.resistor.none"></option>
<option value="up" data-i18n="rpi-gpio.resistor.pullup"></option>
<option value="down" data-i18n="rpi-gpio.resistor.pulldown"></option>
</select>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span data-i18n="rpi-gpio.label.debounce"></span>
<input type="text" id="node-input-debounce" style="width:47px; text-align:right"/>&nbsp;mS
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-read" style="width:70%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
<div class="form-tips"><span data-i18n="[html]rpi-gpio.tip.in"></span></div>
</script>
<script type="text/javascript">
var bcm2pin = {
"2":"3", "3":"5", "4":"7", "14":"8", "15":"10", "17":"11", "18":"12", "27":"13", "22":"15",
"23":"16", "24":"18", "10":"19", "9":"21", "25":"22", "11":"23", "8":"24", "7":"26",
"5":"29", "6":"31", "12":"32", "13":"33", "19":"35", "16":"36", "26":"37", "20":"38", "21":"40"
};
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio in',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"tri",required:true,validate:RED.validators.number() },
intype: { value:"tri" },
debounce: { value:"25" },
read: { value:false }
},
inputs:0,
outputs:1,
icon: "rpi.png",
info: function() {
if ( Object.keys(pinsInUse).length !== 0 ) {
return "**Pins in use** : "+Object.keys(pinsInUse);
}
else { return ""; }
},
label: function() {
var suf = "";
if (this.intype === "up") { suf = "↑ "}
if (this.intype === "down") { suf = "↓ "}
return this.name || "PIN: "+suf+this.pin ;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
outputLabels: function() { return "GPIO"+this.pin; },
oneditprepare: function() {
var pinnow = this.pin;
var pintip = this._("rpi-gpio.tip.pin");
var pinname = this._("rpi-gpio.pinname");
var alreadyuse = this._("rpi-gpio.alreadyuse");
var alreadyset = this._("rpi-gpio.alreadyset");
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html(pintip + Object.keys(data));
});
$("#node-input-pin").on("change", function() {
if ($("#node-input-pin").val()) {
$("#pinform input[value="+$("#node-input-pin").val()+"]").prop('checked', true);
}
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
}
pinnow = pinnew;
}
});
$("#node-input-intype").on("change", function() {
var newtype = $("#node-input-intype").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify(pinname+" "+pinnow+" "+alreadyset+" "+pinsInUse[pinnow],"error");
}
});
$('#pinform input').on('change', function() {
this.pin = $("#pinform input[type='radio']:checked").val();
$("#node-input-pin").val(this.pin);
});
}
});
</script>
<script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row" style="min-width: 540px">
<label><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.pinname"></span></label>
<input type="text" id="node-input-pin" style="display:none;">
<div class="rpi-gpio-pinTable">
<div class="pinTableBody" id="pinform">
<div class="pinTableRow">
<div class="pinTableCellL pinColorPower"><label>3.3V Power - 1 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorPower"><label><input disabled type="radio" name="pins" value=""> 2 - 5V Power</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-3">SDA1 - GPIO02 - 3 <input id="pinTable-pin-3" type="radio" name="pins" value="3"></label></div>
<div class="pinTableCellR pinColorPower"><label><input disabled type="radio" name="pins" value=""> 4 - 5V Power</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-5">SCL1 - GPIO03 - 5 <input id="pinTable-pin-5" type="radio" name="pins" value="5"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 6 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-7">GPIO04 - 7 <input id="pinTable-pin-7" type="radio" name="pins" value="7"></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-8"><input id="pinTable-pin-8" type="radio" name="pins" value="8"> 8 - GPIO14 - TxD</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGround"><label>Ground - 9 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-10"><input id="pinTable-pin-10" type="radio" name="pins" value="10"> 10 - GPIO15 - RxD</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-11">GPIO17 - 11 <input id="pinTable-pin-11" type="radio" name="pins" value="11"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-12"><input id="pinTable-pin-12" type="radio" name="pins" value="12"> 12 - GPIO18</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-13">GPIO27 - 13 <input id="pinTable-pin-13" type="radio" name="pins" value="13"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 14 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-15">GPIO22 - 15 <input id="pinTable-pin-15" type="radio" name="pins" value="15"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-16"><input id="pinTable-pin-16" type="radio" name="pins" value="16"> 16 - GPIO23</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorPower"><label>3.3V Power - 17 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-18"><input id="pinTable-pin-18" type="radio" name="pins" value="18"> 18 - GPIO24</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-19">MOSI - GPIO10 - 19 <input id="pinTable-pin-19" type="radio" name="pins" value="19"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 20 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-21">MISO - GPIO09 - 21 <input id="pinTable-pin-21" type="radio" name="pins" value="21"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-22"><input id="pinTable-pin-22" type="radio" name="pins" value="22"> 22 - GPIO25</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorDual"><label for="pinTable-pin-23">SCLK - GPIO11 - 23 <input id="pinTable-pin-23" type="radio" name="pins" value="23"></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-24"><input id="pinTable-pin-24" type="radio" name="pins" value="24"> 24 - GPIO8 - CE0</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGround"><label>Ground - 25 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorDual"><label for="pinTable-pin-26"><input id="pinTable-pin-26" type="radio" name="pins" value="26"> 26 - GPIO7 - CE1</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorSD"><label>SD - 27 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorSD"><label><input disabled type="radio" name="pins" value=""> 28 - SC</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-29">GPIO05 - 29 <input id="pinTable-pin-29" type="radio" name="pins" value="29"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 30 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-31">GPIO06 - 31 <input id="pinTable-pin-31" type="radio" name="pins" value="31"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-32"><input id="pinTable-pin-32" type="radio" name="pins" value="32"> 32 - GPIO12</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-33">GPIO13 - 33 <input id="pinTable-pin-33" type="radio" name="pins" value="33"></label></div>
<div class="pinTableCellR pinColorGround"><label><input disabled type="radio" name="pins" value=""> 34 - Ground</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-35">GPIO19 - 35 <input id="pinTable-pin-35" type="radio" name="pins" value="35"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-36"><input id="pinTable-pin-36" type="radio" name="pins" value="36"> 36 - GPIO16</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGPIO"><label for="pinTable-pin-37">GPIO26 - 37 <input id="pinTable-pin-37" type="radio" name="pins" value="37"></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-38"><input id="pinTable-pin-38" type="radio" name="pins" value="38"> 38 - GPIO20</label></div>
</div>
<div class="pinTableRow">
<div class="pinTableCellL pinColorGround"><label>Ground - 39 <input disabled type="radio" name="pins" value=""></label></div>
<div class="pinTableCellR pinColorGPIO"><label for="pinTable-pin-40"><input id="pinTable-pin-40" type="radio" name="pins" value="40"> 40 - GPIO21</label></div>
</div>
</div>
</div>
</div>
<div class="form-row" id="node-set-pwm">
<label>&nbsp;&nbsp;&nbsp;&nbsp;<span data-i18n="rpi-gpio.label.type"></span></label>
<select id="node-input-out" style="width: 250px;">
<option value="out" data-i18n="rpi-gpio.digout"></option>
<option value="pwm" data-i18n="rpi-gpio.pwmout"></option>
</select>
</div>
<div class="form-row" id="node-set-tick">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-set" style="width: 70%;"><span data-i18n="rpi-gpio.label.initpin"></span></label>
</div>
<div class="form-row" id="node-set-state">
<label for="node-input-level">&nbsp;</label>
<select id="node-input-level" style="width: 250px;">
<option value="0" data-i18n="rpi-gpio.initpin0"></option>
<option value="1" data-i18n="rpi-gpio.initpin1"></option>
</select>
</div>
<div class="form-row" id="node-set-freq">
<label for="node-input-freq"> <span data-i18n="rpi-gpio.label.freq"></span></label>
<input type="text" id="node-input-freq" placeholder="100"> Hz
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
<div class="form-tips" id="dig-tip"><span data-i18n="[html]rpi-gpio.tip.dig"></span></div>
<div class="form-tips" id="pwm-tip"><span data-i18n="[html]rpi-gpio.tip.pwm"></span></div>
</script>
<script type="text/javascript">
var bcm2pin = {
"2":"3", "3":"5", "4":"7", "14":"8", "15":"10", "17":"11", "18":"12", "27":"13", "22":"15",
"23":"16", "24":"18", "10":"19", "9":"21", "25":"22", "11":"23", "8":"24", "7":"26",
"5":"29", "6":"31", "12":"32", "13":"33", "19":"35", "16":"36", "26":"37", "20":"38", "21":"40"
};
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio out',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.number() },
set: { value:"" },
level: { value:"0" },
freq: {value:""},
out: { value:"out" }
},
inputs:1,
outputs:0,
icon: "rpi.png",
info: function() {
if ( Object.keys(pinsInUse).length !== 0 ) {
return "**Pins in use** : "+Object.keys(pinsInUse);
}
else { return ""; }
},
align: "right",
label: function() {
if (this.out === "pwm") { return this.name || "PWM: "+this.pin; }
else if (this.out === "ser") { return this.name || "Servo: "+this.pin; }
else {
var suf = "";
if (this.set == true) { suf = (this.level === "1") ? " ¹" : " ₀"; }
return this.name||"PIN: "+ this.pin + suf ;
}
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
inputLabels: function() { return "GPIO"+this.pin; },
oneditprepare: function() {
var pinnow = this.pin;
var pintip = this._("rpi-gpio.tip.pin");
var pinname = this._("rpi-gpio.pinname");
var alreadyuse = this._("rpi-gpio.alreadyuse");
var alreadyset = this._("rpi-gpio.alreadyset");
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html(pintip + Object.keys(data));
});
$("#node-input-pin").on("change", function() {
if ($("#node-input-pin").val()) {
$("#pinform input[value="+$("#node-input-pin").val()+"]").prop('checked', true);
}
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
}
pinnow = pinnew;
}
});
$("#node-input-out").on("change", function() {
var newtype = $("#node-input-out").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify(pinname+" "+pinnow+" "+alreadyset+" "+pinsInUse[pinnow],"error");
}
});
var hidestate = function () {
if ($("#node-input-out").val() === "pwm") {
$('#node-set-tick').hide();
$('#node-set-state').hide();
$('#node-input-set').prop('checked', false);
$("#dig-tip").hide();
$("#pwm-tip").show();
$('#node-set-freq').show();
}
else {
$('#node-set-tick').show();
$("#dig-tip").show();
$("#pwm-tip").hide();
$('#node-set-freq').hide();
}
};
$("#node-input-out").on("change", function () { hidestate(); });
hidestate();
var setstate = function () {
if ($('#node-input-set').is(":checked")) {
$("#node-set-state").show();
} else {
$("#node-set-state").hide();
}
};
$("#node-input-set").on("change", function () { setstate(); });
setstate();
$('#pinform input').on('change', function() {
this.pin = $("#pinform input[type='radio']:checked").val();
$("#node-input-pin").val(this.pin);
});
}
});
</script>
<script type="text/x-red" data-template-name="rpi-mouse">
<div class="form-row">
<label for="node-input-butt"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.button"></span></label>
<select type="text" id="node-input-butt" style="width: 250px;">
<option value="1" data-i18n="rpi-gpio.left"></option>
<option value="2" data-i18n="rpi-gpio.right"></option>
<option value="4" data-i18n="rpi-gpio.middle"></option>
<option value="7" data-i18n="rpi-gpio.any"></option>
</select>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-mouse',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
butt: { value:"1",required:true }
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
var na = this._("rpi-gpio.label.pimouse");
if (this.butt === "1") { na += " "+this._("rpi-gpio.label.left"); }
if (this.butt === "2") { na += " "+this._("rpi-gpio.label.right"); }
if (this.butt === "4") { na += " "+this._("rpi-gpio.label.middle"); }
return this.name||na;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="rpi-keyboard">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-keyboard',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" }
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
return this.name || this._("rpi-gpio.label.pikeyboard");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -1,371 +0,0 @@
module.exports = function(RED) {
"use strict";
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var fs = require('fs');
var gpioCommand = __dirname+'/nrgpio';
var allOK = true;
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === -1) {
allOK = false;
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
}
try {
fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
// /usr/lib/python2.7/dist-packages/RPi/GPIO
} catch(err) {
try {
fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
} catch(err) {
try {
fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
} catch(err) {
try {
fs.statSync("/usr/local/lib/python2.7/dist-packages/RPi/GPIO"); // installed with pip
} catch(err) {
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound"));
allOK = false;
}
}
}
}
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
allOK = false;
}
} catch(err) {
allOK = false;
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
}
// the magic to make python print stuff immediately
process.env.PYTHONUNBUFFERED = 1;
var pinsInUse = {};
var pinTypes = {"out":RED._("rpi-gpio.types.digout"), "tri":RED._("rpi-gpio.types.input"), "up":RED._("rpi-gpio.types.pullup"), "down":RED._("rpi-gpio.types.pulldown"), "pwm":RED._("rpi-gpio.types.pwmout")};
function GPIOInNode(n) {
RED.nodes.createNode(this,n);
this.buttonState = -1;
this.pin = n.pin;
this.intype = n.intype;
this.read = n.read || false;
this.debounce = Number(n.debounce || 25);
if (this.read) { this.buttonState = -2; }
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.intype;
}
else {
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
}
}
if (allOK === true) {
if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true;
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
var d = data.toString().trim().split("\n");
for (var i = 0; i < d.length; i++) {
if (d[i] === '') { return; }
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
}
node.buttonState = d[i];
node.status({fill:"green",shape:"dot",text:d[i]});
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
}
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
});
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
}
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
if (node.read === true) {
var val;
if (node.intype == "up") { val = 1; }
if (node.intype == "down") { val = 0; }
setTimeout(function(){
node.send({ topic:"pi/"+node.pin, payload:val });
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})});
},250);
}
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
function GPIOOutNode(n) {
RED.nodes.createNode(this,n);
this.pin = n.pin;
this.set = n.set || false;
this.level = n.level || 0;
this.freq = n.freq || 100;
this.out = n.out || "out";
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.out;
}
else {
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
}
}
function inputlistener(msg) {
if (msg.payload === "true") { msg.payload = true; }
if (msg.payload === "false") { msg.payload = false; }
var out = Number(msg.payload);
var limit = 1;
if (node.out === "pwm") { limit = 100; }
if ((out >= 0) && (out <= limit)) {
if (RED.settings.verbose) { node.log("out: "+out); }
if (node.child !== null) {
node.child.stdin.write(out+"\n");
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
}
else {
node.error(RED._("rpi-gpio.errors.pythoncommandnotfound"),msg);
node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.not-running"});
}
}
else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
}
if (allOK === true) {
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
node.status({fill:"green",shape:"dot",text:node.level});
} else {
node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
}
node.running = true;
node.on("input", inputlistener);
node.child.stdout.on('data', function (data) {
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
}
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
node.on("input", function(msg){
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})});
});
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
function PiMouseNode(n) {
RED.nodes.createNode(this,n);
this.butt = n.butt || 7;
var node = this;
if (allOK === true) {
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
data = Number(data);
if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
}
RED.nodes.registerType("rpi-mouse",PiMouseNode);
function PiKeyboardNode(n) {
RED.nodes.createNode(this,n);
var node = this;
if (allOK === true) {
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
var b = data.toString().trim().split(",");
var act = "up";
if (b[1] === "1") { act = "down"; }
if (b[1] === "2") { act = "repeat"; }
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.status({});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
}
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
var pitype = { type:"" };
if (allOK === true) {
exec(gpioCommand+" info", function(err,stdout,stderr) {
if (err) {
RED.log.info(RED._("rpi-gpio.errors.version"));
}
else {
try {
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
pitype.type = info["TYPE"];
}
catch(e) {
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
}
}
});
}
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype);
});
RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pinsInUse);
});
}

View File

@ -1,17 +0,0 @@
#!/bin/bash
#
# 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.
#
BASEDIR=$(dirname $0)
python -u $BASEDIR/nrgpio.py $@

View File

@ -1,239 +0,0 @@
#!/usr/bin/python
#
# 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.
#
# Import library functions we need
import RPi.GPIO as GPIO
import struct
import sys
import os
import subprocess
from time import sleep
try:
raw_input # Python 2
except NameError:
raw_input = input # Python 3
bounce = 25
if len(sys.argv) > 2:
cmd = sys.argv[1].lower()
pin = int(sys.argv[2])
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
if cmd == "pwm":
#print("Initialised pin "+str(pin)+" to PWM")
try:
freq = int(sys.argv[3])
except:
freq = 100
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, freq)
p.start(0)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
p.ChangeDutyCycle(float(data))
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print("bad data: "+data)
elif cmd == "buzz":
#print("Initialised pin "+str(pin)+" to Buzz")
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.stop()
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
elif float(data) == 0:
p.stop()
else:
p.start(50)
p.ChangeFrequency(float(data))
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print("bad data: "+data)
elif cmd == "out":
#print("Initialised pin "+str(pin)+" to OUT")
GPIO.setup(pin,GPIO.OUT)
if len(sys.argv) == 4:
GPIO.output(pin,int(sys.argv[3]))
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
data = int(data)
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except:
if len(sys.argv) == 4:
data = int(sys.argv[3])
else:
data = 0
if data != 0:
data = 1
GPIO.output(pin,data)
elif cmd == "in":
#print("Initialised pin "+str(pin)+" to IN")
bounce = float(sys.argv[4])
def handle_callback(chan):
sleep(bounce/1000.0)
print(GPIO.input(chan))
if sys.argv[3].lower() == "up":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
elif sys.argv[3].lower() == "down":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_DOWN)
else:
GPIO.setup(pin,GPIO.IN)
print(GPIO.input(pin))
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce))
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
elif cmd == "byte":
#print("Initialised BYTE mode - "+str(pin)+)
list = [7,11,13,12,15,16,18,22]
GPIO.setup(list,GPIO.OUT)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
data = int(data)
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup()
sys.exit(0)
except:
data = 0
for bit in range(8):
if pin == 1:
mask = 1 << (7 - bit)
else:
mask = 1 << bit
GPIO.output(list[bit], data & mask)
elif cmd == "borg":
#print("Initialised BORG mode - "+str(pin)+)
GPIO.setup(11,GPIO.OUT)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(15,GPIO.OUT)
r = GPIO.PWM(11, 100)
g = GPIO.PWM(13, 100)
b = GPIO.PWM(15, 100)
r.start(0)
g.start(0)
b.start(0)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
c = data.split(",")
r.ChangeDutyCycle(float(c[0]))
g.ChangeDutyCycle(float(c[1]))
b.ChangeDutyCycle(float(c[2]))
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup()
sys.exit(0)
except:
data = 0
elif cmd == "mouse": # catch mice button events
file = open( "/dev/input/mice", "rb" )
oldbutt = 0
def getMouseEvent():
global oldbutt
global pin
buf = file.read(3)
pin = pin & 0x07
button = ord( buf[0] ) & pin # mask out just the required button(s)
if button != oldbutt: # only send if changed
oldbutt = button
print(button)
while True:
try:
getMouseEvent()
except:
file.close()
sys.exit(0)
elif cmd == "kbd": # catch keyboard button events
try:
while not os.path.isdir("/dev/input/by-path"):
sleep(10)
infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip()
infile_path = "/dev/input/by-path/" + infile
EVENT_SIZE = struct.calcsize('llHHI')
file = open(infile_path, "rb")
event = file.read(EVENT_SIZE)
while event:
(tv_sec, tv_usec, type, code, value) = struct.unpack('llHHI', event)
#if type != 0 or code != 0 or value != 0:
if type == 1:
# type,code,value
print("%u,%u" % (code, value))
event = file.read(EVENT_SIZE)
print("0,0")
file.close()
sys.exit(0)
except:
file.close()
sys.exit(0)
elif len(sys.argv) > 1:
cmd = sys.argv[1].lower()
if cmd == "rev":
print(GPIO.RPI_REVISION)
elif cmd == "ver":
print(GPIO.VERSION)
elif cmd == "info":
print(GPIO.RPI_INFO)
else:
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")
print(" only ver (gpio version) and info (board information) accept no pin parameter.")
else:
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")

View File

@ -48,6 +48,7 @@
{v:"lte",t:"<=",kind:'V'}, {v:"lte",t:"<=",kind:'V'},
{v:"gt",t:">",kind:'V'}, {v:"gt",t:">",kind:'V'},
{v:"gte",t:">=",kind:'V'}, {v:"gte",t:">=",kind:'V'},
{v:"hask",t:"switch.rules.hask",kind:'V'},
{v:"btwn",t:"switch.rules.btwn",kind:'V'}, {v:"btwn",t:"switch.rules.btwn",kind:'V'},
{v:"cont",t:"switch.rules.cont",kind:'V'}, {v:"cont",t:"switch.rules.cont",kind:'V'},
{v:"regex",t:"switch.rules.regex",kind:'V'}, {v:"regex",t:"switch.rules.regex",kind:'V'},
@ -142,6 +143,7 @@
var typeField = rule.find(".node-input-rule-type-value"); var typeField = rule.find(".node-input-rule-type-value");
var numField = rule.find(".node-input-rule-num-value"); var numField = rule.find(".node-input-rule-num-value");
var expField = rule.find(".node-input-rule-exp-value"); var expField = rule.find(".node-input-rule-exp-value");
var keyField = rule.find(".node-input-rule-key-value");
var btwnField1 = rule.find(".node-input-rule-btwn-value"); var btwnField1 = rule.find(".node-input-rule-btwn-value");
var btwnField2 = rule.find(".node-input-rule-btwn-value2"); var btwnField2 = rule.find(".node-input-rule-btwn-value2");
var selectWidth; var selectWidth;
@ -160,6 +162,8 @@
numField.typedInput("width",(newWidth-selectWidth-70)); numField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "jsonata_exp") { } else if (type === "jsonata_exp") {
expField.typedInput("width",(newWidth-selectWidth-70)); expField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "hask") {
keyField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "istype") { } else if (type === "istype") {
typeField.typedInput("width",(newWidth-selectWidth-70)); typeField.typedInput("width",(newWidth-selectWidth-70));
} else { } else {
@ -214,6 +218,7 @@
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]}); var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3); var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]}); var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
var keyValueField = $('<input/>',{class:"node-input-rule-key-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['str','msg','flow','global','env']});
var typeValueField = $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row) var typeValueField = $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row)
.typedInput({default:'string',types:[ .typedInput({default:'string',types:[
{value:"string",label:"string",hasValue:false}, {value:"string",label:"string",hasValue:false},
@ -236,6 +241,7 @@
if ((type === "btwn") || (type === "index")) { if ((type === "btwn") || (type === "index")) {
valueField.typedInput('hide'); valueField.typedInput('hide');
expValueField.typedInput('hide'); expValueField.typedInput('hide');
keyValueField.typedInput('hide');
numValueField.typedInput('hide'); numValueField.typedInput('hide');
typeValueField.typedInput('hide'); typeValueField.typedInput('hide');
btwnValueField.typedInput('show'); btwnValueField.typedInput('show');
@ -243,19 +249,30 @@
btwnValueField.typedInput('hide'); btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide'); btwnValue2Field.typedInput('hide');
expValueField.typedInput('hide'); expValueField.typedInput('hide');
keyValueField.typedInput('hide');
numValueField.typedInput('show'); numValueField.typedInput('show');
typeValueField.typedInput('hide'); typeValueField.typedInput('hide');
valueField.typedInput('hide'); valueField.typedInput('hide');
} else if (type === "hask") {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('hide');
keyValueField.typedInput('show');
numValueField.typedInput('hide');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
} else if (type === "jsonata_exp") { } else if (type === "jsonata_exp") {
btwnValueField.typedInput('hide'); btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide'); btwnValue2Field.typedInput('hide');
expValueField.typedInput('show'); expValueField.typedInput('show');
keyValueField.typedInput('hide');
numValueField.typedInput('hide'); numValueField.typedInput('hide');
typeValueField.typedInput('hide'); typeValueField.typedInput('hide');
valueField.typedInput('hide'); valueField.typedInput('hide');
} else { } else {
btwnValueField.typedInput('hide'); btwnValueField.typedInput('hide');
expValueField.typedInput('hide'); expValueField.typedInput('hide');
keyValueField.typedInput('hide');
numValueField.typedInput('hide'); numValueField.typedInput('hide');
typeValueField.typedInput('hide'); typeValueField.typedInput('hide');
valueField.typedInput('hide'); valueField.typedInput('hide');
@ -297,7 +314,10 @@
} else if (rule.t === "istype") { } else if (rule.t === "istype") {
typeValueField.typedInput('value',rule.vt); typeValueField.typedInput('value',rule.vt);
typeValueField.typedInput('type',rule.vt); typeValueField.typedInput('type',rule.vt);
} else if (rule.t === "jsonata_exp") { } else if (rule.t === "hask") {
keyValueField.typedInput('value',rule.v);
keyValueField.typedInput('type',rule.vt);
}else if (rule.t === "jsonata_exp") {
expValueField.typedInput('value',rule.v); expValueField.typedInput('value',rule.v);
expValueField.typedInput('type',rule.vt||'jsonata'); expValueField.typedInput('type',rule.vt||'jsonata');
} else if (typeof rule.v != "undefined") { } else if (typeof rule.v != "undefined") {
@ -352,7 +372,6 @@
}, },
oneditsave: function() { oneditsave: function() {
var rules = $("#node-input-rule-container").editableList('items'); var rules = $("#node-input-rule-container").editableList('items');
var ruleset;
var node = this; var node = this;
node.rules = []; node.rules = [];
rules.each(function(i) { rules.each(function(i) {
@ -372,6 +391,9 @@
} else if (type === "istype") { } else if (type === "istype") {
r.v = rule.find(".node-input-rule-type-value").typedInput('type'); r.v = rule.find(".node-input-rule-type-value").typedInput('type');
r.vt = rule.find(".node-input-rule-type-value").typedInput('type'); r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
} else if (type === "hask") {
r.v = rule.find(".node-input-rule-key-value").typedInput('value');
r.vt = rule.find(".node-input-rule-key-value").typedInput('type');
} else if (type === "jsonata_exp") { } else if (type === "jsonata_exp") {
r.v = rule.find(".node-input-rule-exp-value").typedInput('value'); r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type'); r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');

View File

@ -47,7 +47,6 @@ module.exports = function(RED) {
} }
return false; return false;
}, },
'istype': function(a, b) { 'istype': function(a, b) {
if (b === "array") { return Array.isArray(a); } if (b === "array") { return Array.isArray(a); }
else if (b === "buffer") { return Buffer.isBuffer(a); } else if (b === "buffer") { return Buffer.isBuffer(a); }
@ -72,6 +71,9 @@ module.exports = function(RED) {
var index = parts.index; var index = parts.index;
return ((min <= index) && (index <= max)); return ((min <= index) && (index <= max));
}, },
'hask': function(a, b) {
return (typeof b !== "object" ) && a.hasOwnProperty(b+"");
},
'jsonata_exp': function(a, b) { return (b === true); }, 'jsonata_exp': function(a, b) { return (b === true); },
'else': function(a) { return a === true; } 'else': function(a) { return a === true; }
}; };

View File

@ -226,7 +226,6 @@
}, },
oneditsave: function() { oneditsave: function() {
var rules = $("#node-input-rule-container").editableList('items'); var rules = $("#node-input-rule-container").editableList('items');
var ruleset;
var node = this; var node = this;
node.rules= []; node.rules= [];
rules.each(function(i) { rules.each(function(i) {

View File

@ -42,7 +42,7 @@ module.exports = function(RED) {
node.addname = n.addname || ""; node.addname = n.addname || "";
try { try {
if (node.spltType === "str") { if (node.spltType === "str") {
this.splt = (n.splt || "\\n").replace(/\\n/,"\n").replace(/\\r/,"\r").replace(/\\t/,"\t").replace(/\\e/,"\e").replace(/\\f/,"\f").replace(/\\0/,"\0"); this.splt = (n.splt || "\\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
} else if (node.spltType === "bin") { } else if (node.spltType === "bin") {
var spltArray = JSON.parse(n.splt); var spltArray = JSON.parse(n.splt);
if (Array.isArray(spltArray)) { if (Array.isArray(spltArray)) {

View File

@ -1,74 +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.
-->
<script type="text/x-red" data-help-name="rpi-gpio in">
<p> Raspberry Pi-Empfangs-Node. Generiert eine <code>msg.payload</code> mit einem Wert von
0 oder 1 in Abhängigkeit vom Status des Eingabepins. </p>
<h3> Ausgaben </h3>
<dl class="message-properties">
<dt> payload <span class="property-type"> Zahl </span> </dt>
<dd> Die Nutzdaten sind 1 oder 0. </dd>
<dt> topic <span class="property-type"> Zeichenfolge </span> </dt>
<dd> Das Topic wird auf <code>pi/ { the pin number }</code> gesetzt. </dd>
</dl>
<h3> Details </h3>
<p> Sie können auch den Eingangs-Pullup-Widerstand oder den Pulldown-Widerstand aktivieren. </p>
<p> Erfordert die Python-Bibliothek RPi.GPIO in der Version 0.5.10 (oder besser). </p>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p> Raspberry Pi-Ausgabe-Node. Kann im Digital- oder PWM-Modus verwendet werden. </p>
<h3> Eingaben </h3>
<dl class="message-properties">
<dt> payload <span class="property-type"> Zahl | Zeichen | Boolean </span> </dt>
</dl>
<h3> Details </h3>
<p> Der digitale Modus erwartet der Node eine <code>msg.payload</code> von entweder 0 oder 1 (oder false oder true)
und schaltet den ausgewählten physischen Pin in Abhängigkeit von dem übergebenen Wert entweder auf LOW oder HIGH. </p>
<p> Der Anfangswert für den Pin kann bei der Implementierung auf 0 oder 1 gesetzt werden. </p>
<p> Im PWM-Modus erwartet der Node einen Eingabewert mit der Zahl 0-100. Es kann ein Gleitkommazahl sein. </p>
<p> Der PWM-Modus kann verwendet werden, um einen Servo unter Verwendung von Eingabewerten nur zwischen 10 und 20 zu steuern,
akzeptiert aber auch Gleitkommawerte. Der GPIO2-Pin eignet sich am besten dafür, da er Hardware-PWM unterstützt. Für eine bessere Servounterstützung
sollten Sie den alternativen Node <pre>Node-red-node-pi-gpiod</pre> berücksichtigen. </p>
<p> Erfordert die Python-Bibliothek RPi.GPIO in der Version 0.5.10 (oder besser). </p>
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p> Raspberry Pi-Maustasten-Node. Erfordert eine USB-Maus. </p>
<h3>Ausgaben </h3>
<dl class="message-properties">
<dt> payload <span class="property-type"> Zahl </span> </dt>
<dd> 1 oder 0, wenn die ausgewählte Maustaste gedrückt und losgelassen wird. </dd>
<dt> Schaltfläche <span class="property-type"> Zahl </span> </dt>
<dd> 1, 2, 4 entsprechend der linken, der rechten und der mittleren Taste, so dass Sie herausfinden können,
welche Schaltfläche oder Kombination gedrückt wurde. </dd>
<dt> topic <span class="property-type"> Zeichenfolge </span> </dt>
<dd>wird auf: <code>pi/mouse</code> gesetzt</dd>
</dl>
</script>
<script type="text/x-red" data-help-name="rpi-keyboard">
<p>Raspberry Pi Tastatur-Node. Benötigt eine USB Tastatur.</p>
<h3>Ausgaben</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">Zahl</span></dt>
<dd>enthält den Tastaturcode value</dd>
<dt>action <span class="property-type">Zeichenfolge</span></dt>
<dd>wird auf "up", "down", oder "repeat" gesetzt</dd>
<dt>topic <span class="property-type">Zeichenfolge</span></dt>
<dd>wird auf <code>pi/key</code> gesetzt</dd>
</dl>
</script>

View File

@ -729,78 +729,6 @@
"xml_js" : "Dieser Node verarbeitet nur XML-Zeichenfolgen oder JS-Objekte." "xml_js" : "Dieser Node verarbeitet nur XML-Zeichenfolgen oder JS-Objekte."
} }
}, },
"rpi-gpio" : {
"label" : {
"gpiopin" : "GPIO",
"selectpin" : "Auswahlstift",
"resistor" : "Widerstand?",
"readinitial" : "Anfangsstatus des Pins bei Implementierung/Neustart lesen?",
"type" : "Typ",
"initpin" : "Pin-Status initialisieren?",
"debounce" : "Debounce",
"freq" : "Frequenz",
"button" : "Knopf",
"pimouse" : "Pi-Maus",
"pikeyboard" : "Pi-Tastatur",
"left" : "Links",
"right" : "Rechts",
"middle" : "Mitte"
},
"resistor" : {
"none" : "keine",
"pullup" : "pullup",
"pulldown" : "Pulldown"
},
"digout" : "Digitale Ausgabe",
"pwmout" : "PWM-Ausgabe",
"servo" : "Servo-Ausgabe",
"initpin0" : "Anfangsstand des Pin-Niedrig (0)",
"initpin1" : "Anfangsebene von Pin-High (1)",
"left" : "links",
"right" : "rechts",
"middle" : "Mitte",
"any" : "beliebig",
"pinname" : "Pin",
"alreadyuse" : "bereits im Gebrauch",
"alreadyset" : "bereits festgelegt als",
"tip" : {
"pin" : "<b> Verwender Pins </b>: ",
"in" : "Tipp: Es wird nur die digitale Eingabe unterstützt. Die Eingabe muss 0 oder 1 sein.",
"dig" : "Tipp: Für die digitale Ausgabe muss der Wert 0 oder 1 sein.",
"pwm" : "Tipp: Für die PWM-Ausgabe muss der Wert zwischen 0 und 100 liegen; die Einstellung der Hochfrequenz kann mehr CPU beanspruchen als erwartet.",
"ser" : "<b> Tipp </b>: Für die Servo-Ausgabe muss ein Wert zwischen 0 und 100 eingegeben werden. 50 ist das Zentrum."
},
"types" : {
"digout" : "digitale Ausgabe",
"input" : "Eingabe",
"pullup" : "Eingabe mit Pull-up",
"pulldown" : "Eingabe mit Pull-down",
"pwmout" : "PWM-Ausgabe",
"servo" : "Servo-Ausgabe"
},
"status" : {
"stopped" : "Gestoppt",
"closed" : "geschlossen",
"not-running" : "nicht aktiv",
"not-available" : "nicht verfügbar",
"na" : "N/A: __Wert__"
},
"errors" : {
"ignorenode" : "Raspberry Pi-spezifische Nodes inaktiv",
"version" : "Abrufen der Version von Pi fehlgeschlagen",
"sawpitype" : "Saw-Pi-Typ",
"libnotfound" : "Pi RPi.GPIO-Python-Bibliothek nicht gefunden",
"alreadyset" : "GPIO-Stift __pin__ ist bereits als Typ festgelegt: __type__",
"invalidpin" : "Ungültiger GPIO-Pin",
"invalidinput" : "Ungültige Eingabe",
"needtobeexecutable" : "__command__ muss ausführbar sein",
"mustbeexecutable" : "nrgpio muss ausführbar sein",
"commandnotfound" : "Befehl 'nrgpio' nicht gefunden",
"commandnotexecutable" : "nrgpio-Befehl nicht ausführbar",
"error" : "Fehler: __error__",
"pythoncommandnotfound" : "Befehl 'nrpgio python' nicht aktiv"
}
},
"tail" : { "tail" : {
"tail" : "Tail", "tail" : "Tail",
"label" : { "label" : {

View File

@ -22,12 +22,12 @@
<dd>Sets the delay, in milliseconds, to be applied to the message. This <dd>Sets the delay, in milliseconds, to be applied to the message. This
option only applies if the node is configured to allow the message to option only applies if the node is configured to allow the message to
override the configured default delay interval.</dd> override the configured default delay interval.</dd>
<dt class="optional">reset</dt> <dt class="optional">reset</dt>
<dd>If the received message has this property set to any value, all <dd>If the received message has this property set to any value, all
outstanding messages held by the node are cleared without being sent.</dd> outstanding messages held by the node are cleared without being sent.</dd>
<dt class="optional">flush</dt> <dt class="optional">flush</dt>
<dd>If the received message has this property set to any value, all <dd>If the received message has this property set to any value, all
outstanding messages held by the node are sent immediately.</dd> outstanding messages held by the node are sent immediately.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>When configured to delay messages, the delay interval can be a fixed value, <p>When configured to delay messages, the delay interval can be a fixed value,

View File

@ -1,75 +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.
-->
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Pi input node. Generates a <code>msg.payload</code> with either a
0 or 1 depending on the state of the input pin.</p>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd>the payload will be a 1 or a 0.</dd>
<dt>topic <span class="property-type">string</span></dt>
<dd>the topic will be set to <code>pi/{the pin number}</code>.</dd>
</dl>
<h3>Details</h3>
<p>You may also enable the input pullup resistor or the pulldown resistor.</p>
<p>Requires the RPi.GPIO python library version 0.5.10 (or better) in order to work.</p>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Pi output node. Can be used in Digital or PWM modes.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number | string | boolean</span></dt>
</dl>
<h3>Details</h3>
<p>Digital mode - expects a <code>msg.payload</code> with either a 0 or 1 (or true or false),
and will set the selected physical pin high or low depending on the value passed in.</p>
<p>The initial value of the pin at deploy time can also be set to 0 or 1.</p>
<p>PWM mode - expects an input value of a number 0 - 100. It can be floating point.</p>
<p>PWM mode can be used to drive a servo using input values between 10 and 20 only,
but will accept floating point values.
The GPIO2 pin is best for this as it uses hardware to do the PWM. For better servo support
consider the alternative node-red-node-pi-gpiod node.</p>
<p>Requires the RPi.GPIO python library version 0.5.10 (or better) in order to work.</p>
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi mouse button node. Requires a USB mouse.</p>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd>1 or 0 when the selected mouse button is pressed and released.</dd>
<dt>button <span class="property-type">number</span></dt>
<dd>1, 2, 4 corresponding to left, right and middle buttons, so you
can work out which button or combination was pressed.</dd>
<dt>topic <span class="property-type">string</span></dt>
<dd>set to <code>pi/mouse</code></dd>
</dl>
</script>
<script type="text/x-red" data-help-name="rpi-keyboard">
<p>Raspberry Pi keyboard handling node. Requires a USB keyboard.</p>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd>contains the keycode value</dd>
<dt>action <span class="property-type">string</span></dt>
<dd>set to "up", "down", or "repeat"</dd>
<dt>topic <span class="property-type">string</span></dt>
<dd>set to <code>pi/key</code></dd>
</dl>
</script>

View File

@ -621,7 +621,8 @@
"tail":"tail", "tail":"tail",
"index":"index between", "index":"index between",
"exp":"JSONata exp", "exp":"JSONata exp",
"else":"otherwise" "else":"otherwise",
"hask":"has key"
}, },
"errors": { "errors": {
"invalid-expr": "Invalid JSONata expression: __error__", "invalid-expr": "Invalid JSONata expression: __error__",
@ -774,78 +775,6 @@
"xml_js": "This node only handles xml strings or js objects." "xml_js": "This node only handles xml strings or js objects."
} }
}, },
"rpi-gpio": {
"label": {
"gpiopin": "GPIO",
"selectpin": "select pin",
"resistor": "Resistor?",
"readinitial": "Read initial state of pin on deploy/restart?",
"type": "Type",
"initpin": "Initialise pin state?",
"debounce": "Debounce",
"freq": "Frequency",
"button": "Button",
"pimouse": "Pi Mouse",
"pikeyboard": "Pi Keyboard",
"left": "Left",
"right": "Right",
"middle": "Middle"
},
"resistor": {
"none": "none",
"pullup": "pullup",
"pulldown": "pulldown"
},
"digout": "Digital output",
"pwmout": "PWM output",
"servo": "Servo output",
"initpin0": "initial level of pin - low (0)",
"initpin1": "initial level of pin - high (1)",
"left": "left",
"right": "right",
"middle": "middle",
"any": "any",
"pinname": "Pin",
"alreadyuse": "already in use",
"alreadyset": "already set as",
"tip": {
"pin": "<b>Pins in Use</b>: ",
"in": "Tip: Only Digital Input is supported - input must be 0 or 1.",
"dig": "Tip: For digital output - input must be 0 or 1.",
"pwm": "Tip: For PWM output - input must be between 0 to 100; setting high frequency might occupy more CPU than expected.",
"ser": "<b>Tip</b>: For Servo output - input must be between 0 to 100. 50 is centre."
},
"types": {
"digout": "digital output",
"input": "input",
"pullup": "input with pull up",
"pulldown": "input with pull down",
"pwmout": "PWM output",
"servo": "Servo output"
},
"status": {
"stopped": "stopped",
"closed": "closed",
"not-running": "not running",
"not-available": "not available",
"na": "N/A : __value__"
},
"errors": {
"ignorenode": "Raspberry Pi specific node set inactive",
"version": "Failed to get version from Pi",
"sawpitype": "Saw Pi Type",
"libnotfound": "Cannot find Pi RPi.GPIO python library",
"alreadyset": "GPIO pin __pin__ already set as type: __type__",
"invalidpin": "Invalid GPIO pin",
"invalidinput": "Invalid input",
"needtobeexecutable": "__command__ needs to be executable",
"mustbeexecutable": "nrgpio must to be executable",
"commandnotfound": "nrgpio command not found",
"commandnotexecutable": "nrgpio command not executable",
"error": "error: __error__",
"pythoncommandnotfound": "nrpgio python command not running"
}
},
"file": { "file": {
"label": { "label": {
"filename": "Filename", "filename": "Filename",

View File

@ -22,6 +22,8 @@
<dd>メッセージの遅延時間をミリ秒単位で設定します。これはノードの設定でデフォルトの遅延時間を上書きできるようノードを設定した場合にのみ適用します。</dd> <dd>メッセージの遅延時間をミリ秒単位で設定します。これはノードの設定でデフォルトの遅延時間を上書きできるようノードを設定した場合にのみ適用します。</dd>
<dt class="optional">reset</dt> <dt class="optional">reset</dt>
<dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージをクリアします。</dd> <dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージをクリアします。</dd>
<dt class="optional">flush</dt>
<dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージを直ちに送信します。</dd>
</dl> </dl>
<h3>詳細</h3> <h3>詳細</h3>
<p>メッセージを遅延させるように設定する場合、遅延時間は固定値、範囲内の乱数値、メッセージ毎の動的な指定値のいずれかを指定できます。</p> <p>メッセージを遅延させるように設定する場合、遅延時間は固定値、範囲内の乱数値、メッセージ毎の動的な指定値のいずれかを指定できます。</p>

View File

@ -1,71 +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.
-->
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Piの入力ード。入力ピンの状態に応じて、0 または 1 の値を持つ<code>msg.payload</code>を生成します。</p>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値</span></dt>
<dd>ペイロードには、0 または 1 が設定されます。</dd>
<dt>topic <span class="property-type">文字列</span></dt>
<dd>トピックには、<code>pi/{ピン番号}</code>が設定されます。</dd>
</dl>
<h3>詳細</h3>
<p>入力のプルアップ抵抗またはプルダウン抵抗を有効にすることもできます。</p>
<p>動作にはRPi.GPIO pythonライブラリのバージョン 0.5.10 (またはそれ以上)が必要です。</p>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Piの出力ード。デジタルモードまたはPWMモードで利用できます。</p>
<h3>入力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値 | 文字列 | 真偽値</span></dt>
</dl>
<h3>詳細</h3>
<p>デジタルモード - <code>msg.payload</code>に 0 または 1 (あるいは true または false ) を指定すると、入力値に応じて選択された物理ピンにハイまたはローを設定します。</p>
<p>デプロイ時にピンの初期値として 0 または 1 を設定することもできます。</p>
<p>PWMモード - 入力値に 0 から 100 の数値を指定でき。小数値の指定も可能です。</p>
<p>サーボの制御にPWMモードが利用でき、入力に小数値も含む 10 から 20 の値が指定可能です。
PWMを行うハードウェアを利用していることから、PWMモードの指定にはGPIO2ピンが最も適しています。
より良くサーボの制御を行いたい場合は、node-red-node-pi-gpiod ノードの利用も検討してください。</p>
<p>動作にはRPi.GPIO pythonライブラリのバージョン 0.5.10 (またはそれ以上)が必要です。</p>
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi のマウスボタンード。USBマウスが必要です。</p>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値</span></dt>
<dd>選択されたマウスのボタンが押された、または離された場合に 1 または 0 が設定されます。</dd>
<dt>button <span class="property-type">数値</span></dt>
<dd>左、右、真ん中のボタンに応じて 1, 2, 4 が設定され、ボタンあるいはボタンの組み合わせに応じた処理ができます。</dd>
<dt>topic <span class="property-type">文字列</span></dt>
<dd><code>pi/mouse</code>が設定されます。</dd>
</dl>
</script>
<script type="text/x-red" data-help-name="rpi-keyboard">
<p>Raspberry Pi のキーボードを制御するード。USBキーボードが必要です。</p>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値</span></dt>
<dd>キーコードを含みます。</dd>
<dt>action <span class="property-type">文字列</span></dt>
<dd>"up", "down", または "repeat" が設定されます。</dd>
<dt>topic <span class="property-type">文字列</span></dt>
<dd><code>pi/key</code>が設定されます。</dd>
</dl>
</script>

View File

@ -7,7 +7,8 @@
"username": "ユーザ名", "username": "ユーザ名",
"password": "パスワード", "password": "パスワード",
"property": "プロパティ", "property": "プロパティ",
"selectNodes": "ノードを選択..." "selectNodes": "ノードを選択...",
"expand": "展開"
}, },
"status": { "status": {
"connected": "接続済", "connected": "接続済",
@ -137,7 +138,10 @@
"debugNodes": "debugード", "debugNodes": "debugード",
"clearLog": "ログを削除", "clearLog": "ログを削除",
"filterLog": "ログのフィルタリング", "filterLog": "ログのフィルタリング",
"openWindow": "新しいウィンドウで開く" "openWindow": "新しいウィンドウで開く",
"copyPath": "パスをコピー",
"copyPayload": "値をコピー",
"pinPath": "展開を固定"
}, },
"messageMenu": { "messageMenu": {
"collapseAll": "全パスを折りたたむ", "collapseAll": "全パスを折りたたむ",
@ -615,7 +619,8 @@
"tail": "tail", "tail": "tail",
"index": "index between", "index": "index between",
"exp": "JSONata式", "exp": "JSONata式",
"else": "その他" "else": "その他",
"hask": "has key"
}, },
"errors": { "errors": {
"invalid-expr": "不正な表現: __error__", "invalid-expr": "不正な表現: __error__",
@ -768,78 +773,6 @@
"xml_js": "本ードは、XML形式の文字列またはJSONのみ処理します" "xml_js": "本ードは、XML形式の文字列またはJSONのみ処理します"
} }
}, },
"rpi-gpio": {
"label": {
"gpiopin": "GPIO",
"selectpin": "端子の選択",
"resistor": "抵抗",
"readinitial": "デプロイや再起動時に端子の初期状態を読み込む",
"type": "出力形式",
"initpin": "端子の状態を初期化",
"debounce": "デバウンス",
"freq": "頻度",
"button": "ボタン",
"pimouse": "Pi Mouse",
"pikeyboard": "Pi Keyboard",
"left": "Left",
"right": "Right",
"middle": "Middle"
},
"resistor": {
"none": "なし",
"pullup": "プルアップ",
"pulldown": "プルダウン"
},
"digout": "デジタル出力",
"pwmout": "PWM出力",
"servo": "サーボ出力",
"initpin0": "端子の初期レベル - Low (0)",
"initpin1": "端子の初期レベル - High (1)",
"left": "左",
"right": "右",
"middle": "中間",
"any": "全て",
"pinname": "端子",
"alreadyuse": "使用中",
"alreadyset": "設定済",
"tip": {
"pin": "<b>使用中の端子</b>: ",
"in": "注釈: 入力値は、0または1の数値のみ対応しています。",
"dig": "注釈: 「出力形式」として「デジタル出力」を用いる場合、入力値は0または1の数値である必要があります。",
"pwm": "注釈: 「出力形式」として「PWM出力」を用いる場合、入力値は0100の数値である必要があります。",
"ser": "<b>注釈</b>: サーボ出力向け - 入力値は0100の間である必要があります。50が中心値です。"
},
"types": {
"digout": "デジタル出力",
"input": "入力",
"pullup": "プルアップの入力",
"pulldown": "プルダウンの入力",
"pwmout": "PWM出力",
"servo": "サーボ出力"
},
"status": {
"stopped": "停止",
"closed": "切断",
"not-running": "停止中",
"not-available": "利用不可",
"na": "N/A : __value__"
},
"errors": {
"ignorenode": "Raspberry Pi固有のードを無視しました",
"version": "バージョンコマンドが失敗しました",
"sawpitype": "Saw Pi Type",
"libnotfound": "RPi.GPIO pythonライブラリを見つけられませんでした",
"alreadyset": "GPIO端子 __pin__ は既に出力形式が設定されています: __type__",
"invalidpin": "GPIO端子が不正です",
"invalidinput": "入力が不正です",
"needtobeexecutable": "__command__ は実行可能である必要があります",
"mustbeexecutable": "nrgpio は実行可能である必要があります",
"commandnotfound": "nrgpio コマンドが見つかりません",
"commandnotexecutable": "nrgpio コマンドが実行可能ではありません",
"error": "エラー: __error__",
"pythoncommandnotfound": "nrpgio python コマンドが実行されていません"
}
},
"file": { "file": {
"label": { "label": {
"filename": "ファイル名", "filename": "ファイル名",

View File

@ -1,71 +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.
-->
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Pi의 입력노드. 입력 핀의 상태에 따라, 0 또는 1의 값을 갖는 <code>msg.payload</code>을 생성합니다.</p>
<h3>출력</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">수치</span></dt>
<dd>페이로드에는, 0 또는 1이 설정됩니다.</dd>
<dt>topic <span class="property-type">문자열</span></dt>
<dd>토픽에는, <code>pi/{핀 번호}</code>가 설정됩니다.</dd>
</dl>
<h3>상세</h3>
<p>입력의 풀 업 저항 또는, 풀 다운 저항을 유효화 할 수도 있습니다.</p>
<p>작동하려면 RPi.GPIO python라이브러리 버젼0.5.10 (또는 그 이상)이 필요합니다.</p>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Pi의 출력노드. 디지털모드 또는 PWM모드에서 이용할 수 있습니다.</p>
<h3>입력</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">수치 | 문자열 | 진위값</span></dt>
</dl>
<h3>상세</h3>
<p>디지털모드 - <code>msg.payload</code>에 0 또는 1 (혹은 true 또는 false) 을 지정하면, 입력값에 따라 선택된 물리핀에 high 또는 low를 설정합니다.</p>
<p>배포시에 핀의 초기값으로 0 또는 1을 설정할 수도 있습니다.</p>
<p>PWM모드 - 입력값에 0에서 100의 수치를 지정할수 있고, 소수값도 지정할수 있습니다.</p>
<p>서보제어에 PWM모드를 이용할수 있으며, 입력에 소수값을 포함한 10에서 20의 값을 지정할 수 있습니다.
PWM를 실행하는 하드웨어를 이용하여, PWM모드의 지정에는 GPIO2핀이 가장 적합합니다.
보다 좋은 서보제어를 원하는 경우에는, node-red-node-pi-gpiod 노드를 이용할 것을 검토해 주십시오.</p>
<p>작동하려면 RPi.GPIO python라이브러리 버젼0.5.10 (또는 그 이상)이 필요합니다.</p>
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi 의 마우스버튼노드. USB마우스가 필요합니다.</p>
<h3>출력</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">수치</span></dt>
<dd>선택된 마우스의 버튼이 눌려지거나 떨어졌을 경우에 1 또는 0이 설정됩니다.</dd>
<dt>button <span class="property-type">수치</span></dt>
<dd>좌, 우, 중앙 버튼에 따라 1, 2, 4 가 설정되어, 버튼 혹은 버튼의 조합에 따른 처리를 할 수 있습니다.</dd>
<dt>topic <span class="property-type">문자열</span></dt>
<dd><code>pi/mouse</code>이 설정됩니다.</dd>
</dl>
</script>
<script type="text/x-red" data-help-name="rpi-keyboard">
<p>Raspberry Pi 의 키보드를 제어하는 노드. USB키보드가 필요합니다.</p>
<h3>출력</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">수치</span></dt>
<dd>키 코드를 포함합니다.</dd>
<dt>action <span class="property-type">문자열</span></dt>
<dd>"up", "down", 또는 "repeat" 이 설정됩니다.</dd>
<dt>topic <span class="property-type">문자열</span></dt>
<dd><code>pi/key</code>가 설정됩니다.</dd>
</dl>
</script>

View File

@ -774,78 +774,6 @@
"xml_js": "이 노드는, XML형식의 문자열 혹은 JSON만 처리합니다" "xml_js": "이 노드는, XML형식의 문자열 혹은 JSON만 처리합니다"
} }
}, },
"rpi-gpio": {
"label": {
"gpiopin": "GPIO",
"selectpin": "단자의 선택",
"resistor": "저항",
"readinitial": "배포나 재시작시에 단자의 초기상태를 불러옴",
"type": "출력형식",
"initpin": "단자의 상태를 초기화",
"debounce": "디바운스",
"freq": "빈도",
"button": "버튼",
"pimouse": "Pi Mouse",
"pikeyboard": "Pi Keyboard",
"left": "Left",
"right": "Right",
"middle": "Middle"
},
"resistor": {
"none": "없음",
"pullup": "풀 업",
"pulldown": "풀 다운"
},
"digout": "디지털 출력",
"pwmout": "PWM 출력",
"servo": "서보 출력",
"initpin0": "단자의 초기레벨 - Low (0)",
"initpin1": "단자의 초기레벨 - High (1)",
"left": "좌",
"right": "우",
"middle": "중간",
"any": "모두",
"pinname": "단자",
"alreadyuse": "사용중",
"alreadyset": "설정됨",
"tip": {
"pin": "<b>사용중인 단자</b>: ",
"in": "주석: 입력값은, 0 혹은 1의 수치만 대응하고 있습니다.",
"dig": "주석: ’출력형식’으로 ’디지털출력’을 사용하는 경우, 입력값은 0 혹은 1의 수치일 필요가 있습니다.",
"pwm": "주석: ’출력형식’으로 PWM출력을 사용하는 경우, 입력값은 0100의 수치일 필요가 있습니다.",
"ser": "<b>주석</b>: 서보 출력용 - 입력값은 0100 사이일 필요가 있습니다. 50이 중심값입니다."
},
"types": {
"digout": "디지털 출력",
"input": "입력",
"pullup": "풀 업 입력",
"pulldown": "풀 다운 입력",
"pwmout": "PWM 출력",
"servo": "서보 출력"
},
"status": {
"stopped": "정지",
"closed": "절단",
"not-running": "정지중",
"not-available": "이용불가",
"na": "N/A : __value__"
},
"errors": {
"ignorenode": "Raspberry Pi고유의 노드를 무시했습니다",
"version": "버젼커맨드에 실패했습니다",
"sawpitype": "Saw Pi Type",
"libnotfound": "RPi.GPIO python라이브러리를 발견하지 못했습니다",
"alreadyset": "GPIO단자 __pin__ 은 이미 출력형식이 설정되어 있습니다: __type__",
"invalidpin": "GPIO단자가 올바르지 않습니다",
"invalidinput": "입력이 올바르지 않습니다",
"needtobeexecutable": "__command__ 은 실행가능상태일 필요가 있습니다 ",
"mustbeexecutable": "nrgpio 은 실행가능상태일 필요가 있습니다 ",
"commandnotfound": "nrgpio 커맨드를 찾을수 없습니다",
"commandnotexecutable": "nrgpio 커맨드가 실행가능상태가 아닙니다",
"error": "에러: __error__",
"pythoncommandnotfound": "nrpgio python 커맨드가 실행되지 않았습니다"
}
},
"file": { "file": {
"label": { "label": {
"filename": "파일명", "filename": "파일명",

View File

@ -702,76 +702,6 @@
"xml_js": "此节点仅处理XML字符串或JS对象." "xml_js": "此节点仅处理XML字符串或JS对象."
} }
}, },
"rpi-gpio": {
"label": {
"gpiopin": "GPIO",
"selectpin": "选择引脚",
"resistor": "电阻?",
"readinitial": "在部署/重启时读取引脚的初始状态?",
"type": "类型",
"initpin": "初始化引脚状态?",
"debounce": "去抖动",
"freq": "频率",
"button": "按钮",
"pimouse": "Pi鼠标",
"pikeyboard": "Pi键盘",
"left": "左",
"right": "右",
"middle": "中"
},
"resistor": {
"none": "无",
"pullup": "上拉电阻",
"pulldown": "下拉电阻"
},
"digout": "数字输出",
"pwmout": "PWM输出",
"servo": "伺服输出",
"initpin0": "初始引脚电平 - 低(0)",
"initpin1": "初始引脚电平 - 高(1)",
"left": "左",
"right": "右",
"middle": "中",
"any": "任何",
"pinname": "引脚",
"alreadyuse": "已被使用",
"alreadyset": "已被设为",
"tip": {
"pin": "<b>正在使用引脚</b>: ",
"in": "提示: 仅接受数字输入 - 输出必须为0或1.",
"dig": "提示: 如用数字输出 - 输入必须为0或1.",
"pwm": "提示: 如用PWM输出 - 输入必须为0至100之间; 如用高频率可能会比预期占用更多CPU资源.",
"ser": "<b>提示</b>: 如用伺服输出 - 输入必须为0至100之间. 50为中间值."
},
"types": {
"digout": "数字输出",
"input": "输入",
"pullup": "含有上拉电阻的输入",
"pulldown": "含有下拉电阻的输入",
"pwmout": "PWM输出",
"servo": "伺服输出"
},
"status": {
"stopped": "已停止",
"closed": "已关闭",
"not-running": "不运行"
},
"errors": {
"ignorenode": "忽略树莓派的特定节点",
"version": "版本命令失败",
"sawpitype": "查看Pi类型",
"libnotfound": "找不到树莓派RPi.GPIO的python库",
"alreadyset": "GPIO引脚 __pin__ 已经被设定为类型: __type__",
"invalidpin": "无效GPIO引脚",
"invalidinput": "无效输入",
"needtobeexecutable": "__command__须为可运行命令",
"mustbeexecutable": "nrgpio须为可运行",
"commandnotfound": "nrgpio命令不存在",
"commandnotexecutable": "nrgpio命令不可运行",
"error": "错误: __error__",
"pythoncommandnotfound": "nrpgio python命令未处于运行状态"
}
},
"file": { "file": {
"label": { "label": {
"filename": "文件名", "filename": "文件名",

View File

@ -50,7 +50,12 @@ var nodes = {
"sentiment": {module:"node-red-node-sentiment"}, "sentiment": {module:"node-red-node-sentiment"},
"tail": {module:"node-red-node-tail"} "tail": {module:"node-red-node-tail"},
"rpi-gpio in": {module:"node-red-node-pi-gpio"},
"rpi-gpio out": {module:"node-red-node-pi-gpio"},
"rpi-mouse": {module:"node-red-node-pi-gpio"},
"rpi-keyboard": {module:"node-red-node-pi-gpio"}
} }
module.exports = { module.exports = {

View File

@ -67,7 +67,7 @@ var api = module.exports = {
* @param {String} opts.id - the id of the context * @param {String} opts.id - the id of the context
* @param {String} opts.store - the context store * @param {String} opts.store - the context store
* @param {String} opts.key - the context key * @param {String} opts.key - the context key
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - the node information * @return {Promise} - the node information
* @memberof @node-red/runtime_context * @memberof @node-red/runtime_context
*/ */
@ -81,7 +81,7 @@ var api = module.exports = {
var availableStores = runtime.nodes.listContextStores(); var availableStores = runtime.nodes.listContextStores();
//{ default: 'default', stores: [ 'default', 'file' ] } //{ default: 'default', stores: [ 'default', 'file' ] }
if (store && availableStores.stores.indexOf(store) === -1) { if (store && availableStores.stores.indexOf(store) === -1) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -106,7 +106,7 @@ var api = module.exports = {
if (store !== availableStores.default) { if (store !== availableStores.default) {
encoded.store = store; encoded.store = store;
} }
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key}, opts.req);
resolve(encoded); resolve(encoded);
}); });
return; return;
@ -127,7 +127,7 @@ var api = module.exports = {
// TODO: proper error reporting // TODO: proper error reporting
if (!errorReported) { if (!errorReported) {
errorReported = true; errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "unexpected_error"; err.code = "unexpected_error";
err.status = 400; err.status = 400;
@ -139,7 +139,7 @@ var api = module.exports = {
c--; c--;
if (c === 0) { if (c === 0) {
if (!errorReported) { if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result); resolve(result);
} }
} }
@ -147,7 +147,7 @@ var api = module.exports = {
}) })
} }
} else { } else {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve({}); resolve({});
} }
}) })
@ -161,7 +161,7 @@ var api = module.exports = {
* @param {String} opts.id - the id of the context * @param {String} opts.id - the id of the context
* @param {String} opts.store - the context store * @param {String} opts.store - the context store
* @param {String} opts.key - the context key * @param {String} opts.key - the context key
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - the node information * @return {Promise} - the node information
* @memberof @node-red/runtime_context * @memberof @node-red/runtime_context
*/ */
@ -175,7 +175,7 @@ var api = module.exports = {
var availableStores = runtime.nodes.listContextStores(); var availableStores = runtime.nodes.listContextStores();
//{ default: 'default', stores: [ 'default', 'file' ] } //{ default: 'default', stores: [ 'default', 'file' ] }
if (store && availableStores.stores.indexOf(store) === -1) { if (store && availableStores.stores.indexOf(store) === -1) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"},opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -196,13 +196,13 @@ var api = module.exports = {
if (key) { if (key) {
store = store || availableStores.default; store = store || availableStores.default;
ctx.set(key,undefined,store,function(err) { ctx.set(key,undefined,store,function(err) {
runtime.log.audit({event: "context.delete",scope:scope,id:id,store:store,key:key}); runtime.log.audit({event: "context.delete",scope:scope,id:id,store:store,key:key},opts.req);
resolve(); resolve();
}); });
return; return;
} else { } else {
// TODO: support deleting whole context // TODO: support deleting whole context
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"}); runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"},opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -243,7 +243,7 @@ var api = module.exports = {
// }) // })
} }
} else { } else {
runtime.log.audit({event: "context.delete",scope:scope,id:id,store:store,key:key}); runtime.log.audit({event: "context.delete",scope:scope,id:id,store:store,key:key},opts.req);
resolve(); resolve();
} }

View File

@ -43,12 +43,13 @@ var api = module.exports = {
* Gets the current flow configuration * Gets the current flow configuration
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Flows>} - the active flow configuration * @return {Promise<Flows>} - the active flow configuration
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
getFlows: function(opts) { getFlows: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.log.audit({event: "flows.get"}/*,req*/); runtime.log.audit({event: "flows.get"}, opts.req);
return resolve(runtime.nodes.getFlows()); return resolve(runtime.nodes.getFlows());
}); });
}, },
@ -56,6 +57,7 @@ var api = module.exports = {
* Sets the current flow configuration * Sets the current flow configuration
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Flows>} - the active flow configuration * @return {Promise<Flows>} - the active flow configuration
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
@ -64,7 +66,7 @@ var api = module.exports = {
var flows = opts.flows; var flows = opts.flows;
var deploymentType = opts.deploymentType||"full"; var deploymentType = opts.deploymentType||"full";
runtime.log.audit({event: "flows.set",type:deploymentType}/*,req*/); runtime.log.audit({event: "flows.set",type:deploymentType}, opts.req);
var apiPromise; var apiPromise;
if (deploymentType === 'reload') { if (deploymentType === 'reload') {
@ -98,6 +100,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.flow - the flow to add * @param {Object} opts.flow - the flow to add
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the id of the added flow * @return {Promise<String>} - the id of the added flow
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
@ -105,10 +108,10 @@ var api = module.exports = {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
var flow = opts.flow; var flow = opts.flow;
runtime.nodes.addFlow(flow).then(function(id) { runtime.nodes.addFlow(flow).then(function(id) {
runtime.log.audit({event: "flow.add",id:id}); runtime.log.audit({event: "flow.add",id:id}, opts.req);
return resolve(id); return resolve(id);
}).catch(function(err) { }).catch(function(err) {
runtime.log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
}) })
@ -122,6 +125,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.id - the id of the flow to retrieve * @param {Object} opts.id - the id of the flow to retrieve
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Flow>} - the active flow configuration * @return {Promise<Flow>} - the active flow configuration
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
@ -129,10 +133,10 @@ var api = module.exports = {
return new Promise(function (resolve,reject) { return new Promise(function (resolve,reject) {
var flow = runtime.nodes.getFlow(opts.id); var flow = runtime.nodes.getFlow(opts.id);
if (flow) { if (flow) {
runtime.log.audit({event: "flow.get",id:opts.id}); runtime.log.audit({event: "flow.get",id:opts.id}, opts.req);
return resolve(flow); return resolve(flow);
} else { } else {
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}); runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -147,6 +151,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.id - the id of the flow to update * @param {Object} opts.id - the id of the flow to update
* @param {Object} opts.flow - the flow configuration * @param {Object} opts.flow - the flow configuration
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the id of the updated flow * @return {Promise<String>} - the id of the updated flow
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
@ -156,22 +161,22 @@ var api = module.exports = {
var id = opts.id; var id = opts.id;
try { try {
runtime.nodes.updateFlow(id,flow).then(function() { runtime.nodes.updateFlow(id,flow).then(function() {
runtime.log.audit({event: "flow.update",id:id}); runtime.log.audit({event: "flow.update",id:id}, opts.req);
return resolve(id); return resolve(id);
}).catch(function(err) { }).catch(function(err) {
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
}) })
} catch(err) { } catch(err) {
if (err.code === 404) { if (err.code === 404) {
runtime.log.audit({event: "flow.update",id:id,error:"not_found"}); runtime.log.audit({event: "flow.update",id:id,error:"not_found"}, opts.req);
// TODO: this swap around of .code and .status isn't ideal // TODO: this swap around of .code and .status isn't ideal
err.status = 404; err.status = 404;
err.code = "not_found"; err.code = "not_found";
return reject(err); return reject(err);
} else { } else {
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
} }
@ -184,6 +189,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.id - the id of the flow to delete * @param {Object} opts.id - the id of the flow to delete
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - resolves if successful * @return {Promise} - resolves if successful
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
@ -192,22 +198,22 @@ var api = module.exports = {
var id = opts.id; var id = opts.id;
try { try {
runtime.nodes.removeFlow(id).then(function() { runtime.nodes.removeFlow(id).then(function() {
runtime.log.audit({event: "flow.remove",id:id}); runtime.log.audit({event: "flow.remove",id:id}, opts.req);
return resolve(); return resolve();
}).catch(function(err) { }).catch(function(err) {
runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
}); });
} catch(err) { } catch(err) {
if (err.code === 404) { if (err.code === 404) {
runtime.log.audit({event: "flow.remove",id:id,error:"not_found"}); runtime.log.audit({event: "flow.remove",id:id,error:"not_found"}, opts.req);
// TODO: this swap around of .code and .status isn't ideal // TODO: this swap around of .code and .status isn't ideal
err.status = 404; err.status = 404;
err.code = "not_found"; err.code = "not_found";
return reject(err); return reject(err);
} else { } else {
runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
} }
@ -221,12 +227,13 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.type - the node type to return the credential information for * @param {String} opts.type - the node type to return the credential information for
* @param {String} opts.id - the node id * @param {String} opts.id - the node id
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the safe credentials * @return {Promise<Object>} - the safe credentials
* @memberof @node-red/runtime_flows * @memberof @node-red/runtime_flows
*/ */
getNodeCredentials: function(opts) { getNodeCredentials: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}); runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}, opts.req);
var credentials = runtime.nodes.getCredentials(opts.id); var credentials = runtime.nodes.getCredentials(opts.id);
if (!credentials) { if (!credentials) {
return resolve({}); return resolve({});

View File

@ -32,13 +32,14 @@ var api = module.exports = {
* @param {String} opts.library - the library * @param {String} opts.library - the library
* @param {String} opts.type - the type of entry * @param {String} opts.type - the type of entry
* @param {String} opts.path - the path of the entry * @param {String} opts.path - the path of the entry
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String|Object>} - resolves when complete * @return {Promise<String|Object>} - resolves when complete
* @memberof @node-red/runtime_library * @memberof @node-red/runtime_library
*/ */
getEntry: function(opts) { getEntry: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) { runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) {
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path}); runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path}, opts.req);
return resolve(result); return resolve(result);
}).catch(function(err) { }).catch(function(err) {
if (err) { if (err) {
@ -51,10 +52,10 @@ var api = module.exports = {
} else { } else {
err.status = 400; err.status = 400;
} }
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code}); runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code}, opts.req);
return reject(err); return reject(err);
} }
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"}); runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"}, opts.req);
var error = new Error(); var error = new Error();
error.code = "not_found"; error.code = "not_found";
error.status = 404; error.status = 404;
@ -72,22 +73,23 @@ var api = module.exports = {
* @param {String} opts.path - the path of the entry * @param {String} opts.path - the path of the entry
* @param {Object} opts.meta - any meta data associated with the entry * @param {Object} opts.meta - any meta data associated with the entry
* @param {String} opts.body - the body of the entry * @param {String} opts.body - the body of the entry
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - resolves when complete * @return {Promise} - resolves when complete
* @memberof @node-red/runtime_library * @memberof @node-red/runtime_library
*/ */
saveEntry: function(opts) { saveEntry: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() { runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() {
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}); runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}, opts.req);
return resolve(); return resolve();
}).catch(function(err) { }).catch(function(err) {
runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()})); runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()}));
if (err.code === 'forbidden') { if (err.code === 'forbidden') {
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"}); runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"}, opts.req);
err.status = 403; err.status = 403;
return reject(err); return reject(err);
} }
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()}); runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()}, opts.req);
var error = new Error(); var error = new Error();
error.status = 400; error.status = 400;
return reject(error); return reject(error);

View File

@ -48,6 +48,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the node set to return * @param {String} opts.id - the id of the node set to return
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information * @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
@ -56,11 +57,11 @@ var api = module.exports = {
var id = opts.id; var id = opts.id;
var result = runtime.nodes.getNodeInfo(id); var result = runtime.nodes.getNodeInfo(id);
if (result) { if (result) {
runtime.log.audit({event: "nodes.info.get",id:id}); runtime.log.audit({event: "nodes.info.get",id:id}, opts.req);
delete result.loaded; delete result.loaded;
return resolve(result); return resolve(result);
} else { } else {
runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"}); runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -73,12 +74,13 @@ var api = module.exports = {
* Gets the list of node modules installed in the runtime * Gets the list of node modules installed in the runtime
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeList>} - the list of node modules * @return {Promise<NodeList>} - the list of node modules
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
getNodeList: function(opts) { getNodeList: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.log.audit({event: "nodes.list.get"}); runtime.log.audit({event: "nodes.list.get"}, opts.req);
return resolve(runtime.nodes.getNodeList()); return resolve(runtime.nodes.getNodeList());
}) })
}, },
@ -89,6 +91,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the node set to return * @param {String} opts.id - the id of the node set to return
* @param {String} opts.lang - the locale language to return * @param {String} opts.lang - the locale language to return
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the node html content * @return {Promise<String>} - the node html content
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
@ -98,10 +101,10 @@ var api = module.exports = {
var lang = opts.lang; var lang = opts.lang;
var result = runtime.nodes.getNodeConfig(id,lang); var result = runtime.nodes.getNodeConfig(id,lang);
if (result) { if (result) {
runtime.log.audit({event: "nodes.config.get",id:id}); runtime.log.audit({event: "nodes.config.get",id:id}, opts.req);
return resolve(result); return resolve(result);
} else { } else {
runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"}); runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -114,12 +117,13 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.lang - the locale language to return * @param {String} opts.lang - the locale language to return
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the node html content * @return {Promise<String>} - the node html content
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
getNodeConfigs: function(opts) { getNodeConfigs: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.log.audit({event: "nodes.configs.get"}); runtime.log.audit({event: "nodes.configs.get"}, opts.req);
return resolve(runtime.nodes.getNodeConfigs(opts.lang)); return resolve(runtime.nodes.getNodeConfigs(opts.lang));
}); });
}, },
@ -129,6 +133,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to return * @param {String} opts.module - the id of the module to return
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<ModuleInfo>} - the node module info * @return {Promise<ModuleInfo>} - the node module info
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
@ -136,10 +141,10 @@ var api = module.exports = {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
var result = runtime.nodes.getModuleInfo(opts.module); var result = runtime.nodes.getModuleInfo(opts.module);
if (result) { if (result) {
runtime.log.audit({event: "nodes.module.get",id:opts.module}); runtime.log.audit({event: "nodes.module.get",id:opts.module}, opts.req);
return resolve(result); return resolve(result);
} else { } else {
runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"}); runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -154,13 +159,14 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to install * @param {String} opts.module - the id of the module to install
* @param {String} opts.version - (optional) the version of the module to install * @param {String} opts.version - (optional) the version of the module to install
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<ModuleInfo>} - the node module info * @return {Promise<ModuleInfo>} - the node module info
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
addModule: function(opts) { addModule: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) { if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}); runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req);
var err = new Error("Settings unavailable"); var err = new Error("Settings unavailable");
err.code = "settings_unavailable"; err.code = "settings_unavailable";
err.status = 400; err.status = 400;
@ -170,7 +176,7 @@ var api = module.exports = {
var existingModule = runtime.nodes.getModuleInfo(opts.module); var existingModule = runtime.nodes.getModuleInfo(opts.module);
if (existingModule) { if (existingModule) {
if (!opts.version || existingModule.version === opts.version) { if (!opts.version || existingModule.version === opts.version) {
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"}); runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"}, opts.req);
var err = new Error("Module already loaded"); var err = new Error("Module already loaded");
err.code = "module_already_loaded"; err.code = "module_already_loaded";
err.status = 400; err.status = 400;
@ -178,24 +184,24 @@ var api = module.exports = {
} }
} }
runtime.nodes.installModule(opts.module,opts.version).then(function(info) { runtime.nodes.installModule(opts.module,opts.version).then(function(info) {
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version}); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version}, opts.req);
return resolve(info); return resolve(info);
}).catch(function(err) { }).catch(function(err) {
if (err.code === 404) { if (err.code === 404) {
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"}); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"}, opts.req);
// TODO: code/status // TODO: code/status
err.status = 404; err.status = 404;
} else if (err.code) { } else if (err.code) {
err.status = 400; err.status = 400;
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code}); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code}, opts.req);
} else { } else {
err.status = 400; err.status = 400;
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
} }
return reject(err); return reject(err);
}) })
} else { } else {
runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"}); runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"}, opts.req);
var err = new Error("Invalid request"); var err = new Error("Invalid request");
err.code = "invalid_request"; err.code = "invalid_request";
err.status = 400; err.status = 400;
@ -209,13 +215,14 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to remove * @param {String} opts.module - the id of the module to remove
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - resolves when complete * @return {Promise} - resolves when complete
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
removeModule: function(opts) { removeModule: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) { if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}); runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req);
var err = new Error("Settings unavailable"); var err = new Error("Settings unavailable");
err.code = "settings_unavailable"; err.code = "settings_unavailable";
err.status = 400; err.status = 400;
@ -223,7 +230,7 @@ var api = module.exports = {
} }
var module = runtime.nodes.getModuleInfo(opts.module); var module = runtime.nodes.getModuleInfo(opts.module);
if (!module) { if (!module) {
runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"}); runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -231,15 +238,15 @@ var api = module.exports = {
} }
try { try {
runtime.nodes.uninstallModule(opts.module).then(function() { runtime.nodes.uninstallModule(opts.module).then(function() {
runtime.log.audit({event: "nodes.remove",module:opts.module}); runtime.log.audit({event: "nodes.remove",module:opts.module}, opts.req);
resolve(); resolve();
}).catch(function(err) { }).catch(function(err) {
err.status = 400; err.status = 400;
runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
return reject(err); return reject(err);
}) })
} catch(error) { } catch(error) {
runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()}); runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()}, opts.req);
error.status = 400; error.status = 400;
return reject(error); return reject(error);
} }
@ -252,6 +259,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to enable or disable * @param {String} opts.module - the id of the module to enable or disable
* @param {String} opts.enabled - whether the module should be enabled or disabled * @param {String} opts.enabled - whether the module should be enabled or disabled
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<ModuleInfo>} - the module info object * @return {Promise<ModuleInfo>} - the module info object
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
@ -259,7 +267,7 @@ var api = module.exports = {
var mod = opts.module; var mod = opts.module;
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) { if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"}); runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"}, opts.req);
var err = new Error("Settings unavailable"); var err = new Error("Settings unavailable");
err.code = "settings_unavailable"; err.code = "settings_unavailable";
err.status = 400; err.status = 400;
@ -268,7 +276,7 @@ var api = module.exports = {
try { try {
var module = runtime.nodes.getModuleInfo(mod); var module = runtime.nodes.getModuleInfo(mod);
if (!module) { if (!module) {
runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"}); runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -287,7 +295,7 @@ var api = module.exports = {
return reject(err); return reject(err);
}); });
} catch(error) { } catch(error) {
runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()}); runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req);
error.status = 400; error.status = 400;
return reject(error); return reject(error);
} }
@ -300,13 +308,14 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the node-set to enable or disable * @param {String} opts.id - the id of the node-set to enable or disable
* @param {String} opts.enabled - whether the module should be enabled or disabled * @param {String} opts.enabled - whether the module should be enabled or disabled
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<ModuleInfo>} - the module info object * @return {Promise<ModuleInfo>} - the module info object
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
setNodeSetState: function(opts) { setNodeSetState: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) { if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"}); runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"}, opts.req);
var err = new Error("Settings unavailable"); var err = new Error("Settings unavailable");
err.code = "settings_unavailable"; err.code = "settings_unavailable";
err.status = 400; err.status = 400;
@ -318,7 +327,7 @@ var api = module.exports = {
try { try {
var node = runtime.nodes.getNodeInfo(id); var node = runtime.nodes.getNodeInfo(id);
if (!node) { if (!node) {
runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"}); runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"}, opts.req);
var err = new Error(); var err = new Error();
err.code = "not_found"; err.code = "not_found";
err.status = 404; err.status = 404;
@ -326,16 +335,16 @@ var api = module.exports = {
} else { } else {
delete node.loaded; delete node.loaded;
putNode(node,enabled).then(function(result) { putNode(node,enabled).then(function(result) {
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled}); runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled}, opts.req);
return resolve(result); return resolve(result);
}).catch(function(err) { }).catch(function(err) {
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
}); });
} }
} catch(error) { } catch(error) {
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()}); runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req);
error.status = 400; error.status = 400;
return reject(error); return reject(error);
} }
@ -347,6 +356,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US) * @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the message catalogs * @return {Promise<Object>} - the message catalogs
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
@ -376,6 +386,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {User} opts.module - the module * @param {User} opts.module - the module
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US) * @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the message catalog * @return {Promise<Object>} - the message catalog
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
@ -397,12 +408,13 @@ var api = module.exports = {
* Gets the list of all icons available in the modules installed within the runtime * Gets the list of all icons available in the modules installed within the runtime
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<IconList>} - the list of all icons * @return {Promise<IconList>} - the list of all icons
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */
getIconList: function(opts) { getIconList: function(opts) {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
runtime.log.audit({event: "nodes.icons.get"}); runtime.log.audit({event: "nodes.icons.get"}, opts.req);
return resolve(runtime.nodes.getNodeIcons()); return resolve(runtime.nodes.getNodeIcons());
}); });
@ -413,6 +425,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module requesting the icon * @param {String} opts.module - the id of the module requesting the icon
* @param {String} opts.icon - the name of the icon * @param {String} opts.icon - the name of the icon
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Buffer>} - the icon file as a Buffer or null if no icon available * @return {Promise<Buffer>} - the icon file as a Buffer or null if no icon available
* @memberof @node-red/runtime_nodes * @memberof @node-red/runtime_nodes
*/ */

View File

@ -27,11 +27,12 @@ var api = module.exports = {
available: function(opts) { available: function(opts) {
return Promise.resolve(!!runtime.storage.projects); return Promise.resolve(!!runtime.storage.projects);
}, },
/** /**
* List projects known to the runtime * List projects known to the runtime
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -56,10 +57,12 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.project - the project information * @param {Object} opts.project - the project information
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
createProject: function(opts) { createProject: function(opts) {
runtime.log.audit({event: "projects.create",name:opts.project?opts.project.name:"missing-name"}, opts.req);
return runtime.storage.projects.createProject(opts.user, opts.project) return runtime.storage.projects.createProject(opts.user, opts.project)
}, },
@ -69,11 +72,13 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project to initialise * @param {String} opts.id - the id of the project to initialise
* @param {Object} opts.project - the project information * @param {Object} opts.project - the project information
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
initialiseProject: function(opts) { initialiseProject: function(opts) {
// Initialised set when creating default files for an empty repo // Initialised set when creating default files for an empty repo
runtime.log.audit({event: "projects.initialise",id:opts.id}, opts.req);
return runtime.storage.projects.initialiseProject(opts.user, opts.id, opts.project) return runtime.storage.projects.initialiseProject(opts.user, opts.id, opts.project)
}, },
@ -81,6 +86,7 @@ var api = module.exports = {
* Gets the active project * Gets the active project
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the active project * @return {Promise<Object>} - the active project
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -93,11 +99,13 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project to activate * @param {String} opts.id - the id of the project to activate
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
setActiveProject: function(opts) { setActiveProject: function(opts) {
var currentProject = runtime.storage.projects.getActiveProject(opts.user); var currentProject = runtime.storage.projects.getActiveProject(opts.user);
runtime.log.audit({event: "projects.set",id:opts.id}, opts.req);
if (!currentProject || opts.id !== currentProject.name) { if (!currentProject || opts.id !== currentProject.name) {
return runtime.storage.projects.setActiveProject(opts.user, opts.id); return runtime.storage.projects.setActiveProject(opts.user, opts.id);
} else { } else {
@ -110,6 +118,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project to get * @param {String} opts.id - the id of the project to get
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the project metadata * @return {Promise<Object>} - the project metadata
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -123,10 +132,12 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project to update * @param {String} opts.id - the id of the project to update
* @param {Object} opts.project - the project information * @param {Object} opts.project - the project information
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
updateProject: function(opts) { updateProject: function(opts) {
runtime.log.audit({event: "projects.update",id:opts.id}, opts.req);
return runtime.storage.projects.updateProject(opts.user, opts.id, opts.project); return runtime.storage.projects.updateProject(opts.user, opts.id, opts.project);
}, },
@ -135,10 +146,12 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project to update * @param {String} opts.id - the id of the project to update
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
deleteProject: function(opts) { deleteProject: function(opts) {
runtime.log.audit({event: "projects.delete",id:opts.id}, opts.req);
return runtime.storage.projects.deleteProject(opts.user, opts.id); return runtime.storage.projects.deleteProject(opts.user, opts.id);
}, },
@ -148,6 +161,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {Boolean} opts.remote - whether to include status of remote repos * @param {Boolean} opts.remote - whether to include status of remote repos
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the project status * @return {Promise<Object>} - the project status
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -161,6 +175,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {Boolean} opts.remote - whether to return remote branches (true) or local (false) * @param {Boolean} opts.remote - whether to return remote branches (true) or local (false)
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - a list of the local branches * @return {Promise<Object>} - a list of the local branches
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -174,6 +189,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.branch - the name of the branch * @param {String} opts.branch - the name of the branch
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the status of the branch * @return {Promise<Object>} - the status of the branch
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -188,10 +204,12 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.branch - the name of the branch * @param {String} opts.branch - the name of the branch
* @param {Boolean} opts.create - whether to create the branch if it doesn't exist * @param {Boolean} opts.create - whether to create the branch if it doesn't exist
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
setBranch: function(opts) { setBranch: function(opts) {
runtime.log.audit({event: "projects.branch.set",id:opts.id, branch: opts.branch, create:opts.create}, opts.req);
return runtime.storage.projects.setBranch(opts.user, opts.id, opts.branch, opts.create) return runtime.storage.projects.setBranch(opts.user, opts.id, opts.branch, opts.create)
}, },
@ -202,10 +220,12 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.branch - the name of the branch * @param {String} opts.branch - the name of the branch
* @param {Boolean} opts.force - whether to force delete * @param {Boolean} opts.force - whether to force delete
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
deleteBranch: function(opts) { deleteBranch: function(opts) {
runtime.log.audit({event: "projects.branch.delete",id:opts.id, branch: opts.branch, force:opts.force}, opts.req);
return runtime.storage.projects.deleteBranch(opts.user, opts.id, opts.branch, false, opts.force); return runtime.storage.projects.deleteBranch(opts.user, opts.id, opts.branch, false, opts.force);
}, },
@ -215,10 +235,12 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.message - the message to associate with the commit * @param {String} opts.message - the message to associate with the commit
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
commit: function(opts) { commit: function(opts) {
runtime.log.audit({event: "projects.commit",id:opts.id}, opts.req);
return runtime.storage.projects.commit(opts.user, opts.id,{message: opts.message}); return runtime.storage.projects.commit(opts.user, opts.id,{message: opts.message});
}, },
@ -228,6 +250,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.sha - the sha of the commit to return * @param {String} opts.sha - the sha of the commit to return
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the commit details * @return {Promise<Object>} - the commit details
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -242,6 +265,7 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.limit - limit how many to return * @param {String} opts.limit - limit how many to return
* @param {String} opts.before - id of the commit to work back from * @param {String} opts.before - id of the commit to work back from
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Array>} - an array of commits * @return {Promise<Array>} - an array of commits
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -257,10 +281,12 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
abortMerge: function(opts) { abortMerge: function(opts) {
runtime.log.audit({event: "projects.merge.abort",id:opts.id}, opts.req);
return runtime.storage.projects.abortMerge(opts.user, opts.id); return runtime.storage.projects.abortMerge(opts.user, opts.id);
}, },
@ -271,10 +297,12 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.path - the path of the file being merged * @param {String} opts.path - the path of the file being merged
* @param {String} opts.resolutions - how to resolve the merge conflict * @param {String} opts.resolutions - how to resolve the merge conflict
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
resolveMerge: function(opts) { resolveMerge: function(opts) {
runtime.log.audit({event: "projects.merge.resolve",id:opts.id, file:opts.path}, opts.req);
return runtime.storage.projects.resolveMerge(opts.user, opts.id, opts.path, opts.resolution); return runtime.storage.projects.resolveMerge(opts.user, opts.id, opts.path, opts.resolution);
}, },
@ -283,6 +311,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the file listing * @return {Promise<Object>} - the file listing
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -297,6 +326,7 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.path - the path of the file * @param {String} opts.path - the path of the file
* @param {String} opts.tree - the version control tree to use * @param {String} opts.tree - the version control tree to use
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the content of the file * @return {Promise<String>} - the content of the file
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -310,10 +340,12 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String|Array} opts.path - the path of the file, or an array of paths * @param {String|Array} opts.path - the path of the file, or an array of paths
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
stageFile: function(opts) { stageFile: function(opts) {
runtime.log.audit({event: "projects.file.stage",id:opts.id, file:opts.path}, opts.req);
return runtime.storage.projects.stageFile(opts.user, opts.id, opts.path); return runtime.storage.projects.stageFile(opts.user, opts.id, opts.path);
}, },
@ -323,10 +355,12 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.path - the path of the file. If not set, all staged files are unstaged * @param {String} opts.path - the path of the file. If not set, all staged files are unstaged
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
unstageFile: function(opts) { unstageFile: function(opts) {
runtime.log.audit({event: "projects.file.unstage",id:opts.id, file:opts.path}, opts.req);
return runtime.storage.projects.unstageFile(opts.user, opts.id, opts.path); return runtime.storage.projects.unstageFile(opts.user, opts.id, opts.path);
}, },
@ -336,10 +370,12 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.path - the path of the file * @param {String} opts.path - the path of the file
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
revertFile: function(opts) { revertFile: function(opts) {
runtime.log.audit({event: "projects.file.revert",id:opts.id, file:opts.path}, opts.req);
return runtime.storage.projects.revertFile(opts.user, opts.id,opts.path) return runtime.storage.projects.revertFile(opts.user, opts.id,opts.path)
}, },
@ -350,6 +386,7 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.path - the path of the file * @param {String} opts.path - the path of the file
* @param {String} opts.type - the type of diff * @param {String} opts.type - the type of diff
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the requested diff * @return {Promise<Object>} - the requested diff
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -362,6 +399,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - a list of project remotes * @return {Promise<Object>} - a list of project remotes
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
@ -378,10 +416,12 @@ var api = module.exports = {
* @param {Object} opts.remote - the remote metadata * @param {Object} opts.remote - the remote metadata
* @param {String} opts.remote.name - the name of the remote * @param {String} opts.remote.name - the name of the remote
* @param {String} opts.remote.url - the url of the remote * @param {String} opts.remote.url - the url of the remote
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
addRemote: function(opts) { addRemote: function(opts) {
runtime.log.audit({event: "projects.remote.add",id:opts.id, remote:opts.remote.name}, opts.req);
return runtime.storage.projects.addRemote(opts.user, opts.id, opts.remote) return runtime.storage.projects.addRemote(opts.user, opts.id, opts.remote)
}, },
@ -391,10 +431,12 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.remote - the name of the remote * @param {String} opts.remote - the name of the remote
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
removeRemote: function(opts) { removeRemote: function(opts) {
runtime.log.audit({event: "projects.remote.delete",id:opts.id, remote:opts.remote}, opts.req);
return runtime.storage.projects.removeRemote(opts.user, opts.id, opts.remote); return runtime.storage.projects.removeRemote(opts.user, opts.id, opts.remote);
}, },
@ -405,10 +447,12 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {Object} opts.remote - the remote metadata * @param {Object} opts.remote - the remote metadata
* @param {String} opts.remote.name - the name of the remote * @param {String} opts.remote.name - the name of the remote
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
updateRemote: function(opts) { updateRemote: function(opts) {
runtime.log.audit({event: "projects.remote.update",id:opts.id, remote:opts.remote.name}, opts.req);
return runtime.storage.projects.updateRemote(opts.user, opts.id, opts.remote.name, opts.remote) return runtime.storage.projects.updateRemote(opts.user, opts.id, opts.remote.name, opts.remote)
}, },
@ -416,10 +460,15 @@ var api = module.exports = {
* Pull changes from the remote * Pull changes from the remote
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {String} opts.remote - the remote to pull
* @param {Boolean} opts.track - whether to track this remote
* @param {Boolean} opts.allowUnrelatedHistories -
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
pull: function(opts) { pull: function(opts) {
runtime.log.audit({event: "projects.pull",id:opts.id, remote: opts.remote, track:opts.track}, opts.req);
return runtime.storage.projects.pull(opts.user, opts.id, opts.remote, opts.track, opts.allowUnrelatedHistories); return runtime.storage.projects.pull(opts.user, opts.id, opts.remote, opts.track, opts.allowUnrelatedHistories);
}, },
@ -430,10 +479,12 @@ var api = module.exports = {
* @param {String} opts.id - the id of the project * @param {String} opts.id - the id of the project
* @param {String} opts.remote - the name of the remote * @param {String} opts.remote - the name of the remote
* @param {String} opts.track - whether to set the remote as the upstream * @param {String} opts.track - whether to set the remote as the upstream
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - resolves when complete * @return {Promise<Object>} - resolves when complete
* @memberof @node-red/runtime_projects * @memberof @node-red/runtime_projects
*/ */
push: function(opts) { push: function(opts) {
runtime.log.audit({event: "projects.push",id:opts.id, remote: opts.remote, track:opts.track}, opts.req);
return runtime.storage.projects.push(opts.user, opts.id, opts.remote, opts.track); return runtime.storage.projects.push(opts.user, opts.id, opts.remote, opts.track);
} }

View File

@ -60,6 +60,7 @@ var api = module.exports = {
* Gets the runtime settings object * Gets the runtime settings object
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the runtime settings * @return {Promise<Object>} - the runtime settings
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */
@ -125,6 +126,7 @@ var api = module.exports = {
* Gets an individual user's settings object * Gets an individual user's settings object
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the user settings * @return {Promise<Object>} - the user settings
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */
@ -143,6 +145,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.settings - the updates to the user settings * @param {Object} opts.settings - the updates to the user settings
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the user settings * @return {Promise<Object>} - the user settings
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */
@ -158,16 +161,16 @@ var api = module.exports = {
currentSettings = extend(currentSettings, opts.settings); currentSettings = extend(currentSettings, opts.settings);
try { try {
runtime.settings.setUserSettings(username, currentSettings).then(function() { runtime.settings.setUserSettings(username, currentSettings).then(function() {
runtime.log.audit({event: "settings.update",username:username}); runtime.log.audit({event: "settings.update",username:username}, opts.req);
return resolve(); return resolve();
}).catch(function(err) { }).catch(function(err) {
runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
}); });
} catch(err) { } catch(err) {
runtime.log.warn(runtime.log._("settings.user-not-available",{message:runtime.log._("settings.not-available")})); runtime.log.warn(runtime.log._("settings.user-not-available",{message:runtime.log._("settings.not-available")}));
runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}); runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
err.status = 400; err.status = 400;
return reject(err); return reject(err);
} }
@ -178,6 +181,7 @@ var api = module.exports = {
* Gets a list of a user's ssh keys * Gets a list of a user's ssh keys
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the user's ssh keys * @return {Promise<Object>} - the user's ssh keys
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */
@ -198,6 +202,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {User} opts.id - the id of the key to return * @param {User} opts.id - the id of the key to return
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the user's ssh public key * @return {Promise<String>} - the user's ssh public key
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */
@ -229,6 +234,7 @@ var api = module.exports = {
* @param {User} opts.password - (optional) the password for the key pair * @param {User} opts.password - (optional) the password for the key pair
* @param {User} opts.comment - (option) a comment to associate with the key pair * @param {User} opts.comment - (option) a comment to associate with the key pair
* @param {User} opts.size - (optional) the size of the key. Default: 2048 * @param {User} opts.size - (optional) the size of the key. Default: 2048
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<String>} - the id of the generated key * @return {Promise<String>} - the id of the generated key
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */
@ -249,6 +255,7 @@ var api = module.exports = {
* @param {Object} opts * @param {Object} opts
* @param {User} opts.user - the user calling the api * @param {User} opts.user - the user calling the api
* @param {User} opts.id - the id of the key to delete * @param {User} opts.id - the id of the key to delete
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - resolves when deleted * @return {Promise} - resolves when deleted
* @memberof @node-red/runtime_settings * @memberof @node-red/runtime_settings
*/ */

View File

@ -129,7 +129,7 @@ function start() {
log.info(log._("runtime.version",{component:"Node.js ",version:process.version})); log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
if (settings.UNSUPPORTED_VERSION) { if (settings.UNSUPPORTED_VERSION) {
log.error("*****************************************************************"); log.error("*****************************************************************");
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=4"})+" *"); log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=8.9.0"})+" *");
log.error("*****************************************************************"); log.error("*****************************************************************");
events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true}); events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true});
} }

View File

@ -272,7 +272,7 @@ Node.prototype.error = function(logMessage,msg) {
logMessage = logMessage || ""; logMessage = logMessage || "";
} }
var handled = false; var handled = false;
if (msg) { if (msg && typeof msg === 'object') {
handled = this._flow.handleError(this,logMessage,msg); handled = this._flow.handleError(this,logMessage,msg);
} }
if (!handled) { if (!handled) {

View File

@ -234,7 +234,7 @@ function createContext(id,seed,parent) {
if (err.code === "INVALID_EXPR") { if (err.code === "INVALID_EXPR") {
throw err; throw err;
} }
value[0] = undefined; values[0] = undefined;
} }
} }
} else { } else {
@ -246,7 +246,7 @@ function createContext(id,seed,parent) {
if (err.code === "INVALID_EXPR") { if (err.code === "INVALID_EXPR") {
throw err; throw err;
} }
value[i] = undefined; values[i] = undefined;
} }
} }
} }

View File

@ -24,6 +24,54 @@ const flowUtil = require("./util");
var Log; var Log;
/**
* Create deep copy of object
*/
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
/**
* Evaluate Input Value
*/
function evaluateInputValue(value, type, node) {
if (type === "bool") {
return (value === "true") || (value === true);
}
return redUtil.evaluateNodeProperty(value, type, node, null, null);
}
/**
* Compose information object for env var
*/
function composeInfo(info, val) {
var result = {
name: info.name,
type: info.type,
value: val,
};
if (info.ui) {
var ui = info.ui;
result.ui = {
hasUI: ui.hasUI,
icon: ui.icon,
labels: ui.labels,
type: ui.type
};
var retUI = result.ui;
if (ui.type === "input") {
retUI.inputTypes = ui.inputTypes;
}
if (ui.type === "select") {
retUI.menu = ui.menu;
}
if (ui.type === "spinner") {
retUI.spinner = ui.spinner;
}
}
return result;
}
/** /**
* This class represents a subflow - which is handled as a special type of Flow * This class represents a subflow - which is handled as a special type of Flow
@ -95,10 +143,19 @@ class Subflow extends Flow {
var env = []; var env = [];
if (this.subflowDef.env) { if (this.subflowDef.env) {
this.subflowDef.env.forEach(e => { env[e.name] = e; }); this.subflowDef.env.forEach(e => {
env[e.name] = e;
});
} }
if (this.subflowInstance.env) { if (this.subflowInstance.env) {
this.subflowInstance.env.forEach(e => { env[e.name] = e; }); this.subflowInstance.env.forEach(e => {
var old = env[e.name];
var ui = old ? old.ui : null;
env[e.name] = e;
if (ui) {
env[e.name].ui = ui;
}
});
} }
this.env = env; this.env = env;
} }
@ -257,6 +314,7 @@ class Subflow extends Flow {
super.start(diff); super.start(diff);
} }
/** /**
* Get environment variable of subflow * Get environment variable of subflow
* @param {String} name name of env var * @param {String} name name of env var
@ -266,8 +324,16 @@ class Subflow extends Flow {
this.trace("getSetting:"+name); this.trace("getSetting:"+name);
if (!/^\$parent\./.test(name)) { if (!/^\$parent\./.test(name)) {
var env = this.env; var env = this.env;
if (env && env.hasOwnProperty(name)) { var is_info = name.endsWith("_info");
var val = env[name]; var is_type = name.endsWith("_type");
var ename = (is_info || is_type) ? name.substring(0, name.length -5) : name; // 5 = length of "_info"/"_type"
if (env && env.hasOwnProperty(ename)) {
var val = env[ename];
if (is_type) {
return val ? val.type : undefined;
}
// If this is an env type property we need to be careful not // If this is an env type property we need to be careful not
// to get into lookup loops. // to get into lookup loops.
// 1. if the value to lookup is the same as this one, go straight to parent // 1. if the value to lookup is the same as this one, go straight to parent
@ -276,11 +342,15 @@ class Subflow extends Flow {
// See https://github.com/node-red/node-red/issues/2099 // See https://github.com/node-red/node-red/issues/2099
if (val.type !== 'env' || val.value !== name) { if (val.type !== 'env' || val.value !== name) {
let value = val.value; let value = val.value;
if (val.type === 'env') { var type = val.type;
if (type === 'env') {
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
} }
try { try {
var ret = redUtil.evaluateNodeProperty(value, val.type, this.node, null, null); var ret = evaluateInputValue(value, type, this.node);
if (is_info) {
return composeInfo(val, ret);
}
return ret; return ret;
} }
catch (e) { catch (e) {

View File

@ -51,6 +51,12 @@ function runSshKeygenCommand(args,cwd,env) {
resolve(stdout); resolve(stdout);
} }
}); });
child.on('error', function(err) {
if (/ENOENT/.test(err.toString())) {
err.code = "command_not_found";
}
reject(err);
});
}); });
} }

View File

@ -214,7 +214,7 @@ var log = module.exports = {
if (req) { if (req) {
msg.user = req.user; msg.user = req.user;
msg.path = req.path; msg.path = req.path;
msg.ip = (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined; msg.ip = req.ip || (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined;
} }
log.log(msg); log.log(msg);
} }

View File

@ -27,9 +27,9 @@ var apiEnabled = false;
function checkVersion(userSettings) { function checkVersion(userSettings) {
var semver = require('semver'); var semver = require('semver');
if (!semver.satisfies(process.version,">=4.8.0")) { if (!semver.satisfies(process.version,">=8.9.0")) {
// TODO: in the future, make this a hard error. // TODO: in the future, make this a hard error.
// var e = new Error("Unsupported version of node.js"); // var e = new Error("Unsupported version of Node.js");
// e.code = "unsupported_version"; // e.code = "unsupported_version";
// throw e; // throw e;
userSettings.UNSUPPORTED_VERSION = process.version; userSettings.UNSUPPORTED_VERSION = process.version;
@ -39,7 +39,7 @@ function checkVersion(userSettings) {
* This module provides the full Node-RED application, with both the runtime * This module provides the full Node-RED application, with both the runtime
* and editor components built in. * and editor components built in.
* *
* The API this module exposes allows it to be embedded within another node.js * The API this module exposes allows it to be embedded within another Node.js
* application. * application.
* *
* @namespace node-red * @namespace node-red

View File

@ -39,12 +39,9 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.17.1", "express": "4.17.1",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"node-red-node-email": "^1.6.2",
"node-red-node-feedparser": "^0.1.14",
"node-red-node-rbe": "^0.2.4", "node-red-node-rbe": "^0.2.4",
"node-red-node-sentiment": "^0.1.3", "node-red-node-sentiment": "^0.1.3",
"node-red-node-tail": "^0.0.2", "node-red-node-tail": "^0.0.2",
"node-red-node-twitter": "^1.1.5",
"nopt": "4.0.1", "nopt": "4.0.1",
"semver": "6.2.0" "semver": "6.2.0"
}, },

View File

@ -197,10 +197,8 @@ try {
RED.init(server,settings); RED.init(server,settings);
} catch(err) { } catch(err) {
if (err.code == "unsupported_version") { if (err.code == "unsupported_version") {
console.log("Unsupported version of node.js:",process.version); console.log("Unsupported version of Node.js:",process.version);
console.log("Node-RED requires node.js v4 or later"); console.log("Node-RED requires Node.js v8.9.0 or later");
} else if (err.code == "not_built") {
console.log("Node-RED has not been built. See README.md for details");
} else { } else {
console.log("Failed to start server:"); console.log("Failed to start server:");
if (err.stack) { if (err.stack) {

View File

@ -55,7 +55,7 @@ module.exports = {
// The maximum number of messages nodes will buffer internally as part of their // The maximum number of messages nodes will buffer internally as part of their
// operation. This applies across a range of nodes that operate on message sequences. // operation. This applies across a range of nodes that operate on message sequences.
// defaults to no limit. A value of 0 also means no limit is applied. // defaults to no limit. A value of 0 also means no limit is applied.
//nodeMaxMessageBufferLength: 0, //nodeMessageBufferMaxLength: 0,
// To disable the option for using local files for storing keys and certificates in the TLS configuration // To disable the option for using local files for storing keys and certificates in the TLS configuration
// node, set this to true // node, set this to true

View File

@ -0,0 +1,7 @@
npm install --no-save \
grunt-webdriver@^2.0.3 \
wdio-chromedriver-service@^0.1.5 \
wdio-mocha-framework@^0.6.4 \
wdio-spec-reporter@^0.1.5 \
webdriverio@^4.14.1 \
chromedriver@2

View File

@ -1,154 +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 should = require("should");
var rpiNode = require("nr-test-utils").require("@node-red/nodes/core/hardware/36-rpi-gpio.js");
var statusNode = require("nr-test-utils").require("@node-red/nodes/core/core/25-status.js");
var helper = require("node-red-node-test-helper");
var fs = require("fs");
describe('RPI GPIO Node', function() {
before(function(done) {
helper.startServer(done);
});
after(function(done) {
helper.stopServer(done);
});
afterEach(function() {
helper.unload();
});
var checkIgnore = function(done) {
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return ((evt[0].level == 30) && (evt[0].msg.indexOf("rpi-gpio")===0));
});
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("rpi-gpio : rpi-gpio.errors.ignorenode");
done();
} catch(err) {
done(err);
}
},25);
}
it('should load Input node', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }];
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio in');
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === 1) {
done(); // It's ON a PI ... should really do more tests !
} else {
checkIgnore(done);
}
}
catch(e) {
checkIgnore(done);
}
});
});
it('should load Output node', function(done) {
var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }];
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio out');
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === 1) {
done(); // It's ON a PI ... should really do more tests !
} else {
checkIgnore(done);
}
}
catch(e) {
checkIgnore(done);
}
});
});
it('should read a dummy value high (not on Pi)', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", pin:"7", intype:"up", debounce:"25", read:true, wires:[["n2"]] },
{id:"n2", type:"helper"}];
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'pi/7');
msg.should.have.property('payload', 1);
done();
} catch(err) {
done(err);
}
});
});
});
it('should read a dummy value low (not on Pi)', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", pin:"11", intype:"down", debounce:"25", read:true, wires:[["n2"]] },
{id:"n2", type:"helper"}];
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'pi/11');
msg.should.have.property('payload', 0);
done();
} catch(err) {
done(err);
}
});
});
});
it('should be able preset out to a dummy value (not on Pi)', function(done) {
var flow = [{id:"n1", type:"rpi-gpio out", pin:"7", out:"out", level:"0", set:true, freq:"", wires:[], z:"1"},
{id:"n2", type:"status", scope:null, wires:[["n3"]], z:"1"},
{id:"n3", type:"helper", z:"1"}];
helper.load([rpiNode,statusNode], flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var count = 0;
n3.on("input", function(msg) {
// Only check the first status message received as it may get a
// 'closed' status as the test is tidied up.
if (count === 0) {
count++;
try {
msg.should.have.property('status');
msg.status.should.have.property('text', "rpi-gpio.status.na");
done();
} catch(err) {
done(err);
}
}
});
n1.receive({payload:"1"});
});
});
});

View File

@ -254,6 +254,16 @@ describe('switch Node', function() {
genericSwitchTest("gte", 3, true, true, 3, done); genericSwitchTest("gte", 3, true, true, 3, done);
}); });
it('should match if a payload has a required property', function(done) {
genericSwitchTest("hask", "a", true, true, {a:1}, done);
});
it('should not match if a payload does not have a required property', function(done) {
genericSwitchTest("hask", "a", true, false, {b:1}, done);
});
it('should not match if the key is not a string', function(done) {
genericSwitchTest("hask", 1, true, false, {a:1}, done);
});
it('should check if payload is between given values', function(done) { it('should check if payload is between given values', function(done) {
twoFieldSwitchTest("btwn", "3", "5", true, true, 4, done); twoFieldSwitchTest("btwn", "3", "5", true, true, 4, done);
}); });
@ -519,7 +529,6 @@ describe('switch Node', function() {
singularSwitchTest("nempty", true, false, 0, done); singularSwitchTest("nempty", true, false, 0, done);
}); });
it('should check input against a previous value', function(done) { it('should check input against a previous value', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{ "t": "gt", "v": "", "vt": "prev" }],checkall:true,outputs:1,wires:[["helperNode1"]]}, var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{ "t": "gt", "v": "", "vt": "prev" }],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}]; {id:"helperNode1", type:"helper", wires:[]}];

View File

@ -447,4 +447,117 @@ describe('subflow', function() {
}); });
}); });
it('should access env var type of subflow instance', function(done) {
var flow = [
{id:"t0", type:"tab", label:"", disabled:false, info:""},
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
env: [
{name: "K", type: "str", value: "V"}
],
wires:[["n2"]]},
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
// Subflow
{id:"s1", type:"subflow", name:"Subflow", info:"",
in:[{
x:10, y:10,
wires:[ {id:"s1-n1"} ]
}],
out:[{
x:10, y:10,
wires:[ {id:"s1-n1", port:0} ]
}]
},
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
func:"msg.V = env.get('K_type'); return msg;",
wires:[]}
];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property("V", "str");
done();
}
catch (e) {
console.log(e);
done(e);
}
});
n1.receive({payload:"foo"});
});
});
it('should access env var info of subflow instance', function(done) {
var flow = [
{id:"t0", type:"tab", label:"", disabled:false, info:""},
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
env: [
{name: "K", type: "str", value: "V"}
],
wires:[["n2"]]},
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
// Subflow
{id:"s1", type:"subflow", name:"Subflow", info:"",
in:[{
x:10, y:10,
wires:[ {id:"s1-n1"} ]
}],
out:[{
x:10, y:10,
wires:[ {id:"s1-n1", port:0} ]
}],
env:[
{
name: "K", type: "str", value: "",
ui: {
hasUI: true,
icon: "icon",
labels: {
"en-US": "label"
},
type: "input",
inputTypes: {
str: true
}
}
}
]
},
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
func:"msg.V = env.get('K_info'); return msg;",
wires:[]}
];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property("V");
var v = msg.V;
v.should.have.property("name", "K");
v.should.have.property("value", "V");
v.should.have.property("type", "str");
v.should.have.property("ui");
var ui = v.ui;
ui.should.have.property("hasUI", true);
ui.should.have.property("icon", "icon");
ui.should.have.property("type", "input");
ui.should.have.property("labels");
var labels = ui.labels;
labels.should.have.property("en-US", "label");
ui.should.have.property("inputTypes");
var types = ui.inputTypes;
types.should.have.property("str", true);
done();
}
catch (e) {
console.log(e);
done(e);
}
});
n1.receive({payload:"foo"});
});
});
}); });

View File

@ -441,7 +441,7 @@ describe("api/admin/nodes", function() {
nodes.init({ nodes.init({
nodes:{ nodes:{
getModuleCatalog: function(opts) { getModuleCatalog: function(opts) {
return Promise.resolve(opts); return Promise.resolve({a:123});
} }
} }
}); });
@ -452,7 +452,7 @@ describe("api/admin/nodes", function() {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.eql({ module: 'module/set' }); res.body.should.eql({a:123});
done(); done();
}); });
}); });

View File

@ -57,7 +57,9 @@ describe("runtime-api/settings", function() {
}); });
describe("listProjects", function() { describe("listProjects", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
listProjects: sinon.spy(function(user) { listProjects: sinon.spy(function(user) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -105,7 +107,9 @@ describe("runtime-api/settings", function() {
}); });
describe("createProject", function() { describe("createProject", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
createProject: sinon.spy(function(user,project) { createProject: sinon.spy(function(user,project) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -138,7 +142,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("initialiseProject", function() { describe("initialiseProject", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
initialiseProject: sinon.spy(function(user,id,project) { initialiseProject: sinon.spy(function(user,id,project) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -172,7 +178,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getActiveProject", function() { describe("getActiveProject", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getActiveProject: sinon.spy(function(user) { getActiveProject: sinon.spy(function(user) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -209,7 +217,9 @@ describe("runtime-api/settings", function() {
var activeProject; var activeProject;
var runtime; var runtime;
beforeEach(function() { beforeEach(function() {
runtime = {storage: {projects: { runtime = {
log: mockLog(),
storage: {projects: {
getActiveProject: sinon.spy(function() { return activeProject;}), getActiveProject: sinon.spy(function() { return activeProject;}),
setActiveProject: sinon.spy(function(user,id) { setActiveProject: sinon.spy(function(user,id) {
if (user === "error") { if (user === "error") {
@ -258,7 +268,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getProject", function() { describe("getProject", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getProject: sinon.spy(function(user,id) { getProject: sinon.spy(function(user,id) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -291,7 +303,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("updateProject", function() { describe("updateProject", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
updateProject: sinon.spy(function(user,id,project) { updateProject: sinon.spy(function(user,id,project) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -325,7 +339,9 @@ describe("runtime-api/settings", function() {
}); });
describe("deleteProject", function() { describe("deleteProject", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
deleteProject: sinon.spy(function(user,id) { deleteProject: sinon.spy(function(user,id) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -359,7 +375,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getStatus", function() { describe("getStatus", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getStatus: sinon.spy(function(user,id,remote) { getStatus: sinon.spy(function(user,id,remote) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -392,7 +410,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("getBranches", function() { describe("getBranches", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getBranches: sinon.spy(function(user,id,remote) { getBranches: sinon.spy(function(user,id,remote) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -425,7 +445,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("getBranchStatus", function() { describe("getBranchStatus", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getBranchStatus: sinon.spy(function(user,id,branch) { getBranchStatus: sinon.spy(function(user,id,branch) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -459,7 +481,9 @@ describe("runtime-api/settings", function() {
}); });
describe("setBranch", function() { describe("setBranch", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
setBranch: sinon.spy(function(user,id,branch,create) { setBranch: sinon.spy(function(user,id,branch,create) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -493,7 +517,9 @@ describe("runtime-api/settings", function() {
}); });
describe("deleteBranch", function() { describe("deleteBranch", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
deleteBranch: sinon.spy(function(user,id,branch,something,force) { deleteBranch: sinon.spy(function(user,id,branch,something,force) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -527,7 +553,9 @@ describe("runtime-api/settings", function() {
}); });
describe("commit", function() { describe("commit", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
commit: sinon.spy(function(user,id,message) { commit: sinon.spy(function(user,id,message) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -560,7 +588,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("getCommit", function() { describe("getCommit", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getCommit: sinon.spy(function(user,id,sha) { getCommit: sinon.spy(function(user,id,sha) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -594,7 +624,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getCommits", function() { describe("getCommits", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getCommits: sinon.spy(function(user,id,options) { getCommits: sinon.spy(function(user,id,options) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -634,7 +666,9 @@ describe("runtime-api/settings", function() {
}); });
describe("abortMerge", function() { describe("abortMerge", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
abortMerge: sinon.spy(function(user,id) { abortMerge: sinon.spy(function(user,id) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -668,7 +702,9 @@ describe("runtime-api/settings", function() {
}); });
describe("resolveMerge", function() { describe("resolveMerge", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
resolveMerge: sinon.spy(function(user,id,path,resolution) { resolveMerge: sinon.spy(function(user,id,path,resolution) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -702,7 +738,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getFiles", function() { describe("getFiles", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getFiles: sinon.spy(function(user,id) { getFiles: sinon.spy(function(user,id) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -736,7 +774,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getFile", function() { describe("getFile", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getFile: sinon.spy(function(user,id,path,tree) { getFile: sinon.spy(function(user,id,path,tree) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -770,7 +810,9 @@ describe("runtime-api/settings", function() {
}); });
describe("stageFile", function() { describe("stageFile", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
stageFile: sinon.spy(function(user,id,path) { stageFile: sinon.spy(function(user,id,path) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -803,7 +845,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("unstageFile", function() { describe("unstageFile", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
unstageFile: sinon.spy(function(user,id,path) { unstageFile: sinon.spy(function(user,id,path) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -837,7 +881,9 @@ describe("runtime-api/settings", function() {
}); });
describe("revertFile", function() { describe("revertFile", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
revertFile: sinon.spy(function(user,id,path) { revertFile: sinon.spy(function(user,id,path) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -871,7 +917,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getFileDiff", function() { describe("getFileDiff", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getFileDiff: sinon.spy(function(user,id,path,type) { getFileDiff: sinon.spy(function(user,id,path,type) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -905,7 +953,9 @@ describe("runtime-api/settings", function() {
}); });
describe("getRemotes", function() { describe("getRemotes", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
getRemotes: sinon.spy(function(user,id) { getRemotes: sinon.spy(function(user,id) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -939,7 +989,9 @@ describe("runtime-api/settings", function() {
}); });
describe("addRemote", function() { describe("addRemote", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
addRemote: sinon.spy(function(user,id,remote) { addRemote: sinon.spy(function(user,id,remote) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -973,7 +1025,9 @@ describe("runtime-api/settings", function() {
}); });
describe("removeRemote", function() { describe("removeRemote", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
removeRemote: sinon.spy(function(user,id,remote) { removeRemote: sinon.spy(function(user,id,remote) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -1007,7 +1061,9 @@ describe("runtime-api/settings", function() {
}); });
describe("updateRemote", function() { describe("updateRemote", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
updateRemote: sinon.spy(function(user,id,name,remote) { updateRemote: sinon.spy(function(user,id,name,remote) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -1040,7 +1096,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("pull", function() { describe("pull", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
pull: sinon.spy(function(user,id,remote,track,allowUnrelatedHistories) { pull: sinon.spy(function(user,id,remote,track,allowUnrelatedHistories) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");
@ -1073,7 +1131,9 @@ describe("runtime-api/settings", function() {
}); });
}); });
describe("push", function() { describe("push", function() {
var runtime = {storage: {projects: { var runtime = {
log: mockLog(),
storage: {projects: {
push: sinon.spy(function(user,id,remote,track) { push: sinon.spy(function(user,id,remote,track) {
if (user === "error") { if (user === "error") {
var err = new Error("error"); var err = new Error("error");