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

Merge 0.18.5

This commit is contained in:
Nick O'Leary 2018-05-10 21:45:25 +01:00
commit 0c7f4e2168
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
69 changed files with 2396 additions and 882 deletions

View File

@ -1,3 +1,54 @@
#### 0.18.5: Maintenance Release
Projects
- Add clone project to welcome screen
- Handle cloning a project without package.json
- Keep remote branch state in sync between editor and runtime
New Features
- Add type checks to switch node options (#1714)
- add output property select to HTML parse node (#1701)
- Add Prevent Following Redirect to HTTP Request node (#615) (#1684)
- Add debug and trace functions to function node (#1654)
- Enable user defined icon for subflow
- Add MQTT disconnect message and rework broker node UI (#1719)
- Japanese message catalogue updates (#1723)
- Show node load errors in the Palette Manager view
Editor Fixes
- Highlight subflow node when log msg comes from inside Fixes #1698
- Ensure node wires array is not longer than outputs value Fixes #1678
- Allow importing an unknown config node to be undone Fixes #1681
- Ensure keyboard shortcuts get saved in runtime settings Fixes #1696
- Don't mark a subflow changed when actually modified nothing (#1665)
Node Fixes
- bind to correct port when doing udp broadcast/multicast (#1686)
- Provide full error stack in Function node log message (#1700)
- Fix http request doc type Fixes #1690
- Make debug slightly larger to pass WCAG AA rating
- Make core nodes labels more consistent, to close #1673
- Allow template node to be updated more than once Fixes #1671
- Fix the problem that output labels of switch node sometimes disappear (#1664)
- Chinese translations for core nodes (#1607)
Runtime Fixes
- Handle and display for invalid flow credentials when project is disabled #1689 (#1694)
- node-red-pi: fix behavior with old bash version (#1713)
- Fix ENOENT error on first start when no user dir (#1711)
- Handle null error object in Flow.handleError Fixes #1721
- update settings comments to describe how to setup for ipv6 (#1675)
- Remove credential props after diffing flow to prevent future false positives Fixes #1359
- Log error if settings unavailable when saving user settings Fixes #1645
- Keep backup of .config.json
- Add warning if using \_credentialSecret from .config.json
- Filter req.user in /settings to prevent potentially leaking info
#### 0.18.4: Maintenance Release #### 0.18.4: Maintenance Release
Projects Projects

View File

@ -31,7 +31,7 @@ done
# Find the real location of this script # Find the real location of this script
CURRENT_PATH=`pwd` CURRENT_PATH=`pwd`
SCRIPT_PATH="${BASH_SOURCE[0]}"; SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do while [ -h "${SCRIPT_PATH}" ]; do
cd "`dirname "${SCRIPT_PATH}"`" cd "`dirname "${SCRIPT_PATH}"`"
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done done

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) { $(function() {
document.title = document.title+" : "+window.location.hostname; if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
} document.title = document.title+" : "+window.location.hostname;
RED.init(); }
}); RED.init();
});

View File

@ -214,6 +214,18 @@ var RED = (function() {
} }
] ]
} }
} else if (msg.error === "missing_package_file") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Create default package file",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultPackageFile();
}
}
]
}
} else if (msg.error === "project_empty") { } else if (msg.error === "project_empty") {
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
options.buttons = [ options.buttons = [

View File

@ -208,6 +208,8 @@ RED.palette.editor = (function() {
if (nodeEntry) { if (nodeEntry) {
var activeTypeCount = 0; var activeTypeCount = 0;
var typeCount = 0; var typeCount = 0;
var errorCount = 0;
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0; nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {}; nodeEntries[module].setUseCount = {};
@ -216,7 +218,10 @@ RED.palette.editor = (function() {
var inUseCount = 0; var inUseCount = 0;
var set = moduleInfo.sets[setName]; var set = moduleInfo.sets[setName];
var setElements = nodeEntry.sets[setName]; var setElements = nodeEntry.sets[setName];
if (set.err) {
errorCount++;
$("<li>").text(set.err).appendTo(nodeEntry.errorList);
}
if (set.enabled) { if (set.enabled) {
activeTypeCount += set.types.length; activeTypeCount += set.types.length;
} }
@ -255,6 +260,13 @@ RED.palette.editor = (function() {
setElements.setRow.toggleClass("palette-module-set-disabled",!set.enabled); setElements.setRow.toggleClass("palette-module-set-disabled",!set.enabled);
} }
} }
if (errorCount === 0) {
nodeEntry.errorRow.hide()
} else {
nodeEntry.errorRow.show();
}
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount; var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
nodeEntry.setCount.html(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount})); nodeEntry.setCount.html(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
@ -586,6 +598,9 @@ RED.palette.editor = (function() {
$('<span>').html(entry.name).appendTo(titleRow); $('<span>').html(entry.name).appendTo(titleRow);
var metaRow = $('<div class="palette-module-meta palette-module-version"><i class="fa fa-tag"></i></div>').appendTo(headerRow); var metaRow = $('<div class="palette-module-meta palette-module-version"><i class="fa fa-tag"></i></div>').appendTo(headerRow);
var versionSpan = $('<span>').html(entry.version).appendTo(metaRow); var versionSpan = $('<span>').html(entry.version).appendTo(metaRow);
var errorRow = $('<div class="palette-module-meta palette-module-errors"><i class="fa fa-warning"></i></div>').hide().appendTo(headerRow);
var errorList = $('<ul class="palette-module-error-list"></ul>').appendTo(errorRow);
var buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow); var buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow);
var setButton = $('<a href="#" class="editor-button editor-button-small palette-module-set-button"><i class="fa fa-angle-right palette-module-node-chevron"></i> </a>').appendTo(buttonRow); var setButton = $('<a href="#" class="editor-button editor-button-small palette-module-set-button"><i class="fa fa-angle-right palette-module-node-chevron"></i> </a>').appendTo(buttonRow);
var setCount = $('<span>').appendTo(setButton); var setCount = $('<span>').appendTo(setButton);
@ -620,6 +635,8 @@ RED.palette.editor = (function() {
updateButton: updateButton, updateButton: updateButton,
removeButton: removeButton, removeButton: removeButton,
enableButton: enableButton, enableButton: enableButton,
errorRow: errorRow,
errorList: errorList,
setCount: setCount, setCount: setCount,
container: container, container: container,
shade: shade, shade: shade,
@ -651,7 +668,6 @@ RED.palette.editor = (function() {
typeSwatches[t] = $('<span>',{class:"palette-module-type-swatch"}).appendTo(typeDiv); typeSwatches[t] = $('<span>',{class:"palette-module-type-swatch"}).appendTo(typeDiv);
$('<span>',{class:"palette-module-type-node"}).html(t).appendTo(typeDiv); $('<span>',{class:"palette-module-type-node"}).html(t).appendTo(typeDiv);
}) })
var enableButton = $('<a href="#" class="editor-button editor-button-small"></a>').appendTo(buttonGroup); var enableButton = $('<a href="#" class="editor-button editor-button-small"></a>').appendTo(buttonGroup);
enableButton.click(function(evt) { enableButton.click(function(evt) {
evt.preventDefault(); evt.preventDefault();

View File

@ -1255,6 +1255,14 @@ RED.projects.settings = (function() {
text: 'Delete remote', text: 'Delete remote',
click: function() { click: function() {
notification.close(); notification.close();
if (activeProject.git.branches.remote && activeProject.git.branches.remote.indexOf(entry.name+"/") === 0) {
delete activeProject.git.branches.remote;
}
if (activeProject.git.branches.remoteAlt && activeProject.git.branches.remoteAlt.indexOf(entry.name+"/") === 0) {
delete activeProject.git.branches.remoteAlt;
}
var url = "projects/"+activeProject.name+"/remotes/"+entry.name; var url = "projects/"+activeProject.name+"/remotes/"+entry.name;
var options = { var options = {
url: url, url: url,
@ -1276,6 +1284,7 @@ RED.projects.settings = (function() {
activeProject.git.remotes[name] = remote; activeProject.git.remotes[name] = remote;
}); });
} }
delete activeProject.git.branches.remoteAlt;
RED.sidebar.versionControl.refresh(); RED.sidebar.versionControl.refresh();
}); });
}, },

View File

@ -80,6 +80,26 @@ RED.projects = (function() {
$('<p>').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body); $('<p>').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body);
$('<p>').text("If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.").appendTo(body); $('<p>').text("If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.").appendTo(body);
var row = $('<div style="text-align: center"></div>').appendTo(body);
var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Create Project</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>Clone Repository</button>').appendTo(row);
createAsEmpty.click(function(e) {
e.preventDefault();
createProjectOptions = {
action: "create"
}
show('git-config');
})
createAsClone.click(function(e) {
e.preventDefault();
createProjectOptions = {
action: "clone"
}
show('git-config');
})
return container; return container;
}, },
buttons: [ buttons: [
@ -90,13 +110,6 @@ RED.projects = (function() {
createProjectOptions = {}; createProjectOptions = {};
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
},
{
text: "Create your first project", // TODO: nls
class: "primary",
click: function() {
show('git-config');
}
} }
] ]
}, },
@ -170,7 +183,11 @@ RED.projects = (function() {
currentGitSettings.user.name = gitUsernameInput.val(); currentGitSettings.user.name = gitUsernameInput.val();
currentGitSettings.user.email = gitEmailInput.val(); currentGitSettings.user.email = gitEmailInput.val();
RED.settings.set('git', currentGitSettings); RED.settings.set('git', currentGitSettings);
show('project-details'); if (createProjectOptions.action === "create") {
show('project-details');
} else if (createProjectOptions.action === "clone") {
show('clone-project');
}
} }
} }
] ]
@ -303,6 +320,366 @@ RED.projects = (function() {
} }
}; };
})(), })(),
'clone-project': (function() {
var projectNameInput;
var projectSummaryInput;
var projectFlowFileInput;
var projectSecretInput;
var projectSecretSelect;
var copyProject;
var projectRepoInput;
var projectCloneSecret;
var emptyProjectCredentialInput;
var projectRepoUserInput;
var projectRepoPasswordInput;
var projectNameSublabel;
var projectRepoSSHKeySelect;
var projectRepoPassphrase;
var projectRepoRemoteName
var projectRepoBranch;
var selectedProject;
return {
content: function(options) {
var container = $('<div class="projects-dialog-screen-start"></div>');
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Clone a project").appendTo(body);
$('<p>').text("If you already have a git repository containing a project, you can clone it to get started.").appendTo(body);
var projectList = null;
var pendingFormValidation = false;
$.getJSON("projects", function(data) {
projectList = {};
data.projects.forEach(function(p) {
projectList[p] = true;
if (pendingFormValidation) {
pendingFormValidation = false;
validateForm();
}
})
});
var validateForm = function() {
var projectName = projectNameInput.val();
var valid = true;
if (projectNameInputChanged) {
if (projectList === null) {
pendingFormValidation = true;
return;
}
projectNameStatus.empty();
if (!/^[a-zA-Z0-9\-_]+$/.test(projectName) || projectList[projectName]) {
projectNameInput.addClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>').appendTo(projectNameStatus);
projectNameValid = false;
valid = false;
if (projectList[projectName]) {
projectNameSublabel.text("Project already exists");
} else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
}
} else {
projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameValid = true;
}
projectNameLastChecked = projectName;
}
valid = projectNameValid;
var repo = projectRepoInput.val();
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) {
$("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url");
validRepo = false;
}
if (!validRepo) {
if (projectRepoChanged) {
projectRepoInput.addClass("input-error");
}
valid = false;
} else {
projectRepoInput.removeClass("input-error");
}
if (/^https?:\/\//.test(repo)) {
$(".projects-dialog-screen-create-row-creds").show();
$(".projects-dialog-screen-create-row-sshkey").hide();
} else if (/^(?:ssh|[\S]+?@[\S]+?):(?:\/\/)?/.test(repo)) {
$(".projects-dialog-screen-create-row-creds").hide();
$(".projects-dialog-screen-create-row-sshkey").show();
// if ( !getSelectedSSHKey(projectRepoSSHKeySelect) ) {
// valid = false;
// }
} else {
$(".projects-dialog-screen-create-row-creds").hide();
$(".projects-dialog-screen-create-row-sshkey").hide();
}
$("#projects-dialog-clone-project").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
}
var row;
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
var projectNameStatus = $('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
var projectNameInputChanged = false;
var projectNameLastChecked = "";
var projectNameValid;
var checkProjectName;
var autoInsertedName = "";
projectNameInput.on("change keyup paste",function() {
projectNameInputChanged = (projectNameInput.val() !== projectNameLastChecked);
if (checkProjectName) {
clearTimeout(checkProjectName);
} else if (projectNameInputChanged) {
projectNameStatus.empty();
$('<img src="red/images/spin.svg"/>').appendTo(projectNameStatus);
if (projectNameInput.val() === '') {
validateForm();
return;
}
}
checkProjectName = setTimeout(function() {
validateForm();
checkProjectName = null;
},300)
});
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row);
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row);
var projectRepoChanged = false;
var lastProjectRepo = "";
projectRepoInput.on("change keyup paste",function() {
projectRepoChanged = true;
var repo = $(this).val();
if (lastProjectRepo !== repo) {
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
}
lastProjectRepo = repo;
var m = /\/([^/]+?)(?:\.git)?$/.exec(repo);
if (m) {
var projectName = projectNameInput.val();
if (projectName === "" || projectName === autoInsertedName) {
autoInsertedName = m[1];
projectNameInput.val(autoInsertedName);
projectNameInput.change();
}
}
validateForm();
});
var cloneAuthRows = $('<div class="projects-dialog-screen-create-row"></div>').appendTo(body);
row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows);
$('<div><i class="fa fa-warning"></i> Authentication failed</div>').appendTo(row);
// Repo credentials - username/password ----------------
row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows);
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow);
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
// -----------------------------------------------------
// Repo credentials - key/passphrase -------------------
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows);
subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Key</label>').appendTo(subrow);
projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow);
$.getJSON("settings/user/keys", function(data) {
var count = 0;
data.keys.forEach(function(key) {
projectRepoSSHKeySelect.append($("<option></option>").val(key.name).text(key.name));
count++;
});
if (count === 0) {
projectRepoSSHKeySelect.addClass("input-error");
projectRepoSSHKeySelect.attr("disabled",true);
sshwarningRow.show();
} else {
projectRepoSSHKeySelect.removeClass("input-error");
projectRepoSSHKeySelect.attr("disabled",false);
sshwarningRow.hide();
}
});
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">Passphrase</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> Before you can clone a repository over ssh you must add an SSH key to access it.</div>').appendTo(sshwarningRow);
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
$('<button class="editor-button">Add an ssh key</button>').appendTo(subrow).click(function(e) {
e.preventDefault();
$('#projects-dialog-cancel').click();
RED.userSettings.show('gitconfig');
setTimeout(function() {
$("#user-settings-gitconfig-add-key").click();
},500);
});
// -----------------------------------------------------
// Secret - clone
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label>Credentials encryption key</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row);
return container;
},
buttons: function(options) {
return [
{
text: "Back",
click: function() {
show('git-config');
}
},
{
id: "projects-dialog-clone-project",
disabled: true,
text: "Clone project", // TODO: nls
class: "primary disabled",
click: function() {
var projectType = $(".projects-dialog-screen-create-type.selected").data('type');
var projectData = {
name: projectNameInput.val(),
}
projectData.credentialSecret = projectSecretInput.val();
var repoUrl = projectRepoInput.val();
var metaData = {};
if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repoUrl)) {
var selected = projectRepoSSHKeySelect.val();//false;//getSelectedSSHKey(projectRepoSSHKeySelect);
if ( selected ) {
projectData.git = {
remotes: {
'origin': {
url: repoUrl,
keyFile: selected,
passphrase: projectRepoPassphrase.val()
}
}
};
}
else {
console.log("Error! Can't get selected SSH key path.");
return;
}
}
else {
projectData.git = {
remotes: {
'origin': {
url: repoUrl,
username: projectRepoUserInput.val(),
password: projectRepoPasswordInput.val()
}
}
};
}
$(".projects-dialog-screen-create-row-auth-error").hide();
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
projectRepoUserInput.removeClass("input-error");
projectRepoPasswordInput.removeClass("input-error");
projectRepoSSHKeySelect.removeClass("input-error");
projectRepoPassphrase.removeClass("input-error");
RED.deploy.setDeployInflight(true);
RED.projects.settings.switchProject(projectData.name);
sendRequest({
url: "projects",
type: "POST",
handleAuthFail: false,
responses: {
200: function(data) {
dialog.dialog( "close" );
},
400: {
'project_exists': function(error) {
console.log("already exists");
},
'git_error': function(error) {
console.log("git error",error);
},
'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Connection failed");
},
'git_not_a_repository': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository");
},
'git_repository_not_found': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Repository not found");
},
'git_auth_failed': function(error) {
$(".projects-dialog-screen-create-row-auth-error").show();
projectRepoUserInput.addClass("input-error");
projectRepoPasswordInput.addClass("input-error");
// getRepoAuthDetails(req);
projectRepoSSHKeySelect.addClass("input-error");
projectRepoPassphrase.addClass("input-error");
},
'missing_flow_file': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'project_empty': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'credentials_load_failed': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'*': function(error) {
reportUnexpectedError(error);
$( dialog ).dialog( "close" );
}
}
}
},projectData).then(function() {
RED.events.emit("project:change", {name:name});
}).always(function() {
setTimeout(function() {
RED.deploy.setDeployInflight(false);
},500);
})
}
}
]
}
}
})(),
'default-files': (function() { 'default-files': (function() {
var projectFlowFileInput; var projectFlowFileInput;
var projectCredentialFileInput; var projectCredentialFileInput;
@ -1127,6 +1504,7 @@ RED.projects = (function() {
} }
$(".projects-dialog-screen-create-row-auth-error").hide(); $(".projects-dialog-screen-create-row-auth-error").hide();
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
projectRepoUserInput.removeClass("input-error"); projectRepoUserInput.removeClass("input-error");
projectRepoPasswordInput.removeClass("input-error"); projectRepoPasswordInput.removeClass("input-error");
@ -1873,6 +2251,43 @@ RED.projects = (function() {
createProjectOptions = {}; createProjectOptions = {};
show('default-files',{existingProject: true}); show('default-files',{existingProject: true});
} }
function createDefaultPackageFile() {
RED.deploy.setDeployInflight(true);
RED.projects.settings.switchProject(activeProject.name);
var method = "PUT";
var url = "projects/"+activeProject.name;
var createProjectOptions = {
initialise: true
};
sendRequest({
url: url,
type: method,
requireCleanWorkspace: true,
handleAuthFail: false,
responses: {
200: function(data) { },
400: {
'git_error': function(error) {
console.log("git error",error);
},
'missing_flow_file': function(error) {
// This is a natural next error - but let the runtime event
// trigger the dialog rather than double-report it.
$( dialog ).dialog( "close" );
},
'*': function(error) {
reportUnexpectedError(error);
$( dialog ).dialog( "close" );
}
}
}
},createProjectOptions).always(function() {
setTimeout(function() {
RED.deploy.setDeployInflight(false);
},500);
})
}
function refresh(done) { function refresh(done) {
$.getJSON("projects",function(data) { $.getJSON("projects",function(data) {
@ -1952,6 +2367,7 @@ RED.projects = (function() {
RED.projects.settings.show('deps'); RED.projects.settings.show('deps');
}, },
createDefaultFileSet: createDefaultFileSet, createDefaultFileSet: createDefaultFileSet,
createDefaultPackageFile: createDefaultPackageFile,
// showSidebar: showSidebar, // showSidebar: showSidebar,
refresh: refresh, refresh: refresh,
editProject: function() { editProject: function() {

View File

@ -592,7 +592,10 @@ RED.sidebar.versionControl = (function() {
closeBranchBox(); closeBranchBox();
localCommitListShade.show(); localCommitListShade.show();
$(this).addClass('selected'); $(this).addClass('selected');
var activeProject = RED.projects.getActiveProject();
$("#sidebar-version-control-repo-toolbar-set-upstream-row").toggle(!!activeProject.git.branches.remoteAlt);
remoteBox.show(); remoteBox.show();
setTimeout(function() { setTimeout(function() {
remoteBox.css("height","265px"); remoteBox.css("height","265px");
},100); },100);
@ -868,7 +871,8 @@ RED.sidebar.versionControl = (function() {
if (activeProject.git.branches.remoteAlt) { if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt; url+="/"+activeProject.git.branches.remoteAlt;
} }
if ($("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked')) { var setUpstream = $("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked');
if (setUpstream) {
url+="?u=true" url+="?u=true"
} }
utils.sendRequest({ utils.sendRequest({
@ -880,6 +884,10 @@ RED.sidebar.versionControl = (function() {
// done(error,null); // done(error,null);
}, },
200: function(data) { 200: function(data) {
if (setUpstream && activeProject.git.branches.remoteAlt) {
activeProject.git.branches.remote = activeProject.git.branches.remoteAlt;
delete activeProject.git.branches.remoteAlt;
}
refresh(true); refresh(true);
closeRemoteBox(); closeRemoteBox();
}, },
@ -928,6 +936,10 @@ RED.sidebar.versionControl = (function() {
// done(error,null); // done(error,null);
}, },
200: function(data) { 200: function(data) {
if (options.setUpstream && activeProject.git.branches.remoteAlt) {
activeProject.git.branches.remote = activeProject.git.branches.remoteAlt;
delete activeProject.git.branches.remoteAlt;
}
refresh(true); refresh(true);
closeRemoteBox(); closeRemoteBox();
}, },

View File

@ -49,7 +49,12 @@
.palette-module-version { .palette-module-version {
color: #aaa; color: #aaa;
} }
.palette-module-errors .fa-warning {
opacity: 0.5;
}
ul.palette-module-error-list li {
color: #aaa;
}
} }
@ -222,6 +227,20 @@
margin-left: 5px; margin-left: 5px;
} }
} }
.palette-module-meta .fa-warning {
color: #AD1625;
}
ul.palette-module-error-list {
display: inline-block;
list-style-type: none;
margin: 0;
font-size: 0.9em;
li {
border: none;
background: none;
}
}
.palette-module-shade { .palette-module-shade {
@include shade; @include shade;
text-align: center; text-align: center;

View File

@ -139,16 +139,17 @@
} }
} }
button.editor-button { button.editor-button {
width: calc(50% - 40px); width: calc(50% - 80px);
margin: 20px; margin: 20px;
height: 175px; height: auto;
line-height: 2em; line-height: 2em;
font-size: 1.5em !important; padding: 10px;
border-color: #aaa;
i { i {
color: #ccc; color: #aaa;
} }
&:hover i { &:hover i {
color: #aaa; color: #999;
} }
} }
.button-group { .button-group {
@ -160,7 +161,6 @@
button.projects-dialog-screen-create-type { button.projects-dialog-screen-create-type {
height: auto; height: auto;
padding: 10px; padding: 10px;
} }
.button-group { .button-group {
text-align: center; text-align: center;

View File

@ -204,7 +204,14 @@ module.exports = function(RED) {
} }
var context = vm.createContext(sandbox); var context = vm.createContext(sandbox);
try { try {
this.script = vm.createScript(functionText); this.script = vm.createScript(functionText, {
filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces
displayErrors: true
// Using the following options causes node 4/6 to not include the line number
// in the stack output. So don't use them.
// lineOffset: -11, // line number offset to be used for stack traces
// columnOffset: 0, // column number offset to be used for stack traces
});
this.on("input", function(msg) { this.on("input", function(msg) {
try { try {
var start = process.hrtime(); var start = process.hrtime();
@ -219,6 +226,13 @@ module.exports = function(RED) {
this.status({fill:"yellow",shape:"dot",text:""+converted}); this.status({fill:"yellow",shape:"dot",text:""+converted});
} }
} catch(err) { } catch(err) {
//remove unwanted part
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
var stack = err.stack.split(/\r?\n/);
//store the error in msg to be used in flows
msg.error = err;
var line = 0; var line = 0;
var errorMessage; var errorMessage;

View File

@ -212,48 +212,84 @@
<input type="password" id="node-config-input-password"> <input type="password" id="node-config-input-password">
</div> </div>
</div> </div>
<div id="mqtt-broker-tab-birth" style="display:none"> <div id="mqtt-broker-tab-messages" style="display:none">
<div class="form-row"> <div id="mqtt-broker-section-birth">
<label for="node-config-input-birthTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label> <div class="palette-header">
<input type="text" id="node-config-input-birthTopic" data-i18n="[placeholder]mqtt.placeholder.birth-topic"> <i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.birth-message"></span>
</div>
<div class="section-content" style="padding:10px 0 0 10px">
<div class="form-row">
<label style="width: 100px !important;" for="node-config-input-birthTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-birthTopic" data-i18n="[placeholder]mqtt.placeholder.birth-topic">
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-birthRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
<select id="node-config-input-birthRetain" style="width:75px !important">
<option value="false" data-i18n="mqtt.false"></option>
<option value="true" data-i18n="mqtt.true"></option>
</select>
</div>
<div class="form-row">
<label style="width: 100px !important;" for="node-config-input-birthPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-birthPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-birthQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-config-input-birthQos" style="width:75px !important">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
</div>
</div> </div>
<div class="form-row"> <div id="mqtt-broker-section-close">
<label for="node-config-input-birthQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label> <div class="palette-header">
<select id="node-config-input-birthQos" style="width:125px !important"> <i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.close-message"></span>
<option value="0">0</option> </div>
<option value="1">1</option> <div class="section-content" style="padding:10px 0 0 10px">
<option value="2">2</option> <div class="form-row">
</select> <label style="width: 100px !important;" for="node-config-input-closeTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-config-input-birthRetain" style="width:125px !important"> <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-closeTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.close-topic">
<option value="false" data-i18n="mqtt.false"></option> <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-closeRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
<option value="true" data-i18n="mqtt.true"></option> <select id="node-config-input-closeRetain" style="width:75px !important">
</select> <option value="false" data-i18n="mqtt.false"></option>
<option value="true" data-i18n="mqtt.true"></option>
</select>
</div>
<div class="form-row">
<label style="width: 100px !important;" for="node-config-input-closePayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-closePayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
<label style="margin-left: 10px; width: 90px !important;" for="node-config-input-closeQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-config-input-closeQos" style="width:75px !important">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
</div>
</div> </div>
<div class="form-row"> <div id="mqtt-broker-section-will">
<label for="node-config-input-birthPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label> <div class="palette-header">
<input type="text" id="node-config-input-birthPayload" data-i18n="[placeholder]common.label.payload"> <i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.will-message"></span>
</div> </div>
</div> <div class="section-content" style="padding:10px 0 0 10px">
<div id="mqtt-broker-tab-will" style="display:none"> <div class="form-row">
<div class="form-row"> <label style="width: 100px !important;" for="node-config-input-willTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<label for="node-config-input-willTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label> <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-willTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.will-topic">
<input type="text" id="node-config-input-willTopic" data-i18n="[placeholder]mqtt.placeholder.will-topic"> <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-willRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
</div> <select id="node-config-input-willRetain" style="width:75px !important">
<div class="form-row"> <option value="false" data-i18n="mqtt.false"></option>
<label for="node-config-input-willQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label> <option value="true" data-i18n="mqtt.true"></option>
<select id="node-config-input-willQos" style="width:125px !important"> </select>
<option value="0">0</option> </div>
<option value="1">1</option> <div class="form-row">
<option value="2">2</option> <label style="width: 100px !important;" for="node-config-input-willPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
</select> <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-willPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-config-input-willRetain" style="width:125px !important"> <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-willQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<option value="false" data-i18n="mqtt.false"></option> <select id="node-config-input-willQos" style="width:75px !important">
<option value="true" data-i18n="mqtt.true"></option> <option value="0">0</option>
</select> <option value="1">1</option>
</div> <option value="2">2</option>
<div class="form-row"> </select>
<label for="node-config-input-willPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label> </div>
<input type="text" id="node-config-input-willPayload" data-i18n="[placeholder]common.label.payload"> </div>
</div> </div>
</div> </div>
</div> </div>
@ -269,6 +305,9 @@
<h4>Birth Message</h4> <h4>Birth Message</h4>
<p>This is a message that will be published on the configured topic whenever the <p>This is a message that will be published on the configured topic whenever the
connection is established.</p> connection is established.</p>
<h4>Close Message</h4>
<p>This is a message that will be published on the configured topic before the
connection is closed normally, either by re-deploying the node, or by shutting down.</p>
<h4>Will Message</h4> <h4>Will Message</h4>
<p>This is a message that will be published by the broker in the event the node <p>This is a message that will be published by the broker in the event the node
unexpectedly loses its connection.</p> unexpectedly loses its connection.</p>
@ -300,14 +339,18 @@
compatmode: { value: true}, compatmode: { value: true},
keepalive: {value:60,validate:RED.validators.number()}, keepalive: {value:60,validate:RED.validators.number()},
cleansession: {value: true}, cleansession: {value: true},
willTopic: {value:""},
willQos: {value:"0"},
willRetain: {value:false},
willPayload: {value:""},
birthTopic: {value:""}, birthTopic: {value:""},
birthQos: {value:"0"}, birthQos: {value:"0"},
birthRetain: {value:false}, birthRetain: {value:false},
birthPayload: {value:""} birthPayload: {value:""},
closeTopic: {value:""},
closeQos: {value:"0"},
closeRetain: {value:false},
closePayload: {value:""},
willTopic: {value:""},
willQos: {value:"0"},
willRetain: {value:false},
willPayload: {value:""}
}, },
credentials: { credentials: {
user: {type:"text"}, user: {type:"text"},
@ -343,14 +386,39 @@
id: "mqtt-broker-tab-security", id: "mqtt-broker-tab-security",
label: this._("mqtt.tabs-label.security") label: this._("mqtt.tabs-label.security")
}); });
tabs.addTab({ tabs.addTab({
id: "mqtt-broker-tab-birth", id: "mqtt-broker-tab-messages",
label: this._("mqtt.tabs-label.birth") label: this._("mqtt.tabs-label.messages")
});
tabs.addTab({
id: "mqtt-broker-tab-will",
label: this._("mqtt.tabs-label.will")
}); });
function setUpSection(sectionId, isExpanded) {
var birthMessageSection = $(sectionId);
var paletteHeader = birthMessageSection.find('.palette-header');
var twistie = paletteHeader.find('i');
var sectionContent = birthMessageSection.find('.section-content');
function toggleSection(expanded) {
twistie.toggleClass('expanded', expanded);
sectionContent.toggle(expanded);
}
paletteHeader.click(function(e) {
e.preventDefault();
var isExpanded = twistie.hasClass('expanded');
toggleSection(!isExpanded);
});
toggleSection(isExpanded);
}
// show first section if none are set so the user gets the idea
var showBirthSection = this.birthTopic !== ""
|| this.willTopic === ""
&& this.birthTopic === ""
&& this.closeTopic == "";
setUpSection('#mqtt-broker-section-birth', showBirthSection);
setUpSection('#mqtt-broker-section-close', this.closeTopic !== "");
setUpSection('#mqtt-broker-section-will', this.willTopic !== "");
setTimeout(function() { tabs.resize(); },0); setTimeout(function() { tabs.resize(); },0);
if (typeof this.cleansession === 'undefined') { if (typeof this.cleansession === 'undefined') {
this.cleansession = true; this.cleansession = true;
@ -368,14 +436,18 @@
this.keepalive = 15; this.keepalive = 15;
$("#node-config-input-keepalive").val(this.keepalive); $("#node-config-input-keepalive").val(this.keepalive);
} }
if (typeof this.willQos === 'undefined') {
this.willQos = "0";
$("#node-config-input-willQos").val("0");
}
if (typeof this.birthQos === 'undefined') { if (typeof this.birthQos === 'undefined') {
this.birthQos = "0"; this.birthQos = "0";
$("#node-config-input-birthQos").val("0"); $("#node-config-input-birthQos").val("0");
} }
if (typeof this.closeQos === 'undefined') {
this.willQos = "0";
$("#node-config-input-willQos").val("0");
}
if (typeof this.willQos === 'undefined') {
this.willQos = "0";
$("#node-config-input-willQos").val("0");
}
function updateTLSOptions() { function updateTLSOptions() {
if ($("#node-config-input-usetls").is(':checked')) { if ($("#node-config-input-usetls").is(':checked')) {

View File

@ -60,6 +60,15 @@ module.exports = function(RED) {
}; };
} }
if (n.closeTopic) {
this.closeMessage = {
topic: n.closeTopic,
payload: n.closePayload || "",
qos: Number(n.closeQos||0),
retain: n.closeRetain=="true"|| n.closeRetain===true
};
}
if (this.credentials) { if (this.credentials) {
this.username = this.credentials.user; this.username = this.credentials.user;
this.password = this.credentials.password; this.password = this.credentials.password;
@ -314,6 +323,10 @@ module.exports = function(RED) {
this.on('close', function(done) { this.on('close', function(done) {
this.closing = true; this.closing = true;
if (this.connected) { if (this.connected) {
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage);
}
this.client.once('close', function() { this.client.once('close', function() {
done(); done();
}); });

View File

@ -29,7 +29,7 @@
</div> </div>
<div class="form-row node-input-iface"> <div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label> <label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interfaceprompt"> <input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interfaceprompt">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label> <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>

View File

@ -16,6 +16,7 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var os = require('os');
var dgram = require('dgram'); var dgram = require('dgram');
var udpInputPortsInUse = {}; var udpInputPortsInUse = {};
@ -30,6 +31,29 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4"; this.ipv = n.ipv || "udp4";
var node = this; var node = this;
if (node.iface && node.iface.indexOf(".") === -1) {
try {
if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][1].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][0].address;
}
}
else {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][0].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][1].address;
}
}
}
catch(e) {
node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
node.iface = null;
}
}
var opts = {type:node.ipv, reuseAddr:true}; var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var server; var server;
@ -39,7 +63,7 @@ module.exports = function(RED) {
udpInputPortsInUse[this.port] = server; udpInputPortsInUse[this.port] = server;
} }
else { else {
node.warn(RED._("udp.errors.alreadyused",node.port)); node.warn(RED._("udp.errors.alreadyused",{port:node.port}));
server = udpInputPortsInUse[this.port]; // re-use existing server = udpInputPortsInUse[this.port]; // re-use existing
} }
@ -121,12 +145,38 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4"; this.ipv = n.ipv || "udp4";
var node = this; var node = this;
if (node.iface && node.iface.indexOf(".") === -1) {
try {
if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][1].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][0].address;
}
}
else {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][0].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][1].address;
}
}
}
catch(e) {
node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
node.iface = null;
}
}
var opts = {type:node.ipv, reuseAddr:true}; var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var sock; var sock;
if (udpInputPortsInUse[this.outport || this.port]) { var p = this.port;
sock = udpInputPortsInUse[this.outport || this.port]; if (node.multicast != "false") { p = this.outport||"0"; }
if (udpInputPortsInUse[p]) {
sock = udpInputPortsInUse[p];
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
} }
else { else {
sock = dgram.createSocket(opts); // default to udp4 sock = dgram.createSocket(opts); // default to udp4
@ -136,36 +186,35 @@ module.exports = function(RED) {
// prevent it going to the global error handler and shutting node-red // prevent it going to the global error handler and shutting node-red
// down. // down.
}); });
udpInputPortsInUse[this.outport || this.port] = sock; udpInputPortsInUse[p] = sock;
}
if (node.multicast != "false") { if (node.multicast != "false") {
if (node.outport === "") { node.outport = node.port; } sock.bind(node.outport, function() { // have to bind before you can enable broadcast...
sock.bind(node.outport, function() { // have to bind before you can enable broadcast... sock.setBroadcast(true); // turn on broadcast
sock.setBroadcast(true); // turn on broadcast if (node.multicast == "multi") {
if (node.multicast == "multi") { try {
try { sock.setMulticastTTL(128);
sock.setMulticastTTL(128); sock.addMembership(node.addr,node.iface); // Add to the multicast group
sock.addMembership(node.addr,node.iface); // Add to the multicast group node.log(RED._("udp.status.mc-ready",{iface:node.iface,outport:node.outport,host:node.addr,port:node.port}));
node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port})); } catch (e) {
} catch (e) { if (e.errno == "EINVAL") {
if (e.errno == "EINVAL") { node.error(RED._("udp.errors.bad-mcaddress"));
node.error(RED._("udp.errors.bad-mcaddress")); } else if (e.errno == "ENODEV") {
} else if (e.errno == "ENODEV") { node.error(RED._("udp.errors.interface"));
node.error(RED._("udp.errors.interface")); } else {
} else { node.error(RED._("udp.errors.error",{error:e.errno}));
node.error(RED._("udp.errors.error",{error:e.errno})); }
} }
} else {
node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port}));
} }
} else { });
node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port})); } else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) {
} sock.bind(node.outport);
}); node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port}));
} else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) { } else {
sock.bind(node.outport); node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port})); }
} else {
node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
} }
node.on("input", function(msg) { node.on("input", function(msg) {
@ -198,8 +247,8 @@ module.exports = function(RED) {
}); });
node.on("close", function() { node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.outport || node.port)) { if (udpInputPortsInUse.hasOwnProperty(p)) {
delete udpInputPortsInUse[node.outport || node.port]; delete udpInputPortsInUse[p];
} }
try { try {
sock.close(); sock.close();

View File

@ -323,6 +323,7 @@
"broker": "Server", "broker": "Server",
"example": "e.g. localhost", "example": "e.g. localhost",
"qos": "QoS", "qos": "QoS",
"retain": "Retain",
"clientid": "Client ID", "clientid": "Client ID",
"port": "Port", "port": "Port",
"keepalive": "Keep alive time (s)", "keepalive": "Keep alive time (s)",
@ -332,17 +333,22 @@
"verify-server-cert":"Verify server certificate", "verify-server-cert":"Verify server certificate",
"compatmode": "Use legacy MQTT 3.1 support" "compatmode": "Use legacy MQTT 3.1 support"
}, },
"sections-label":{
"birth-message": "Message sent on connection (birth message)",
"will-message":"Message sent on an unexpected disconnection (will message)",
"close-message":"Message sent before disconnecting (close message)"
},
"tabs-label": { "tabs-label": {
"connection": "Connection", "connection": "Connection",
"security": "Security", "security": "Security",
"will": "Will Message", "messages": "Messages"
"birth": "Birth Message"
}, },
"placeholder": { "placeholder": {
"clientid": "Leave blank for auto generated", "clientid": "Leave blank for auto generated",
"clientid-nonclean":"Must be set for non-clean sessions", "clientid-nonclean":"Must be set for non-clean sessions",
"will-topic": "Leave blank to disable will message", "will-topic": "Leave blank to disable will message",
"birth-topic": "Leave blank to disable birth message" "birth-topic": "Leave blank to disable birth message",
"close-topic": "Leave blank to disable close message"
}, },
"state": { "state": {
"connected": "Connected to broker: __broker__", "connected": "Connected to broker: __broker__",
@ -495,15 +501,15 @@
"using": "using", "using": "using",
"output": "Output", "output": "Output",
"group": "Group", "group": "Group",
"interface": "Local IP", "interface": "Local IF",
"interfaceprompt": "(optional) local ip address to bind to",
"send": "Send a", "send": "Send a",
"toport": "to port", "toport": "to port",
"address": "Address", "address": "Address",
"decode-base64": "Decode Base64 encoded payload?" "decode-base64": "Decode Base64 encoded payload?"
}, },
"placeholder": { "placeholder": {
"interface": "(optional) ip address of eth0", "interface": "(optional) local interface or address to bind to",
"interfaceprompt": "(optional) local interface or address to bind to",
"address": "destination ip" "address": "destination ip"
}, },
"udpmsgs": "udp messages", "udpmsgs": "udp messages",
@ -531,10 +537,11 @@
"mc-group": "udp multicast group __group__", "mc-group": "udp multicast group __group__",
"listener-stopped": "udp listener stopped", "listener-stopped": "udp listener stopped",
"output-stopped": "udp output stopped", "output-stopped": "udp output stopped",
"mc-ready": "udp multicast ready: __outport__ -> __host__:__port__", "mc-ready": "udp multicast ready: __iface__:__outport__ -> __host__:__port__",
"bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__", "bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__",
"ready": "udp ready: __outport__ -> __host__:__port__", "ready": "udp ready: __outport__ -> __host__:__port__",
"ready-nolocal": "udp ready: __host__:__port__" "ready-nolocal": "udp ready: __host__:__port__",
"re-use": "udp re-use socket: __outport__ -> __host__:__port__"
}, },
"errors": { "errors": {
"access-error": "UDP access error, you may need root access for ports below 1024", "access-error": "UDP access error, you may need root access for ports below 1024",
@ -544,7 +551,8 @@
"ip-notset": "udp: ip address not set", "ip-notset": "udp: ip address not set",
"port-notset": "udp: port not set", "port-notset": "udp: port not set",
"port-invalid": "udp: port number not valid", "port-invalid": "udp: port number not valid",
"alreadyused": "udp: port already in use" "alreadyused": "udp: port __port__ already in use",
"ifnotfound": "udp: interface __iface__ not found"
} }
}, },
"switch": { "switch": {
@ -566,6 +574,7 @@
"false":"is false", "false":"is false",
"null":"is null", "null":"is null",
"nnull":"is not null", "nnull":"is not null",
"istype":"is of type",
"head":"head", "head":"head",
"tail":"tail", "tail":"tail",
"index":"index between", "index":"index between",
@ -671,7 +680,8 @@
"html": { "html": {
"label": { "label": {
"select": "Selector", "select": "Selector",
"output": "Output" "output": "Output",
"in": "in"
}, },
"output": { "output": {
"html": "the html content of the elements", "html": "the html content of the elements",

View File

@ -327,17 +327,22 @@
"verify-server-cert": "サーバの証明書を確認", "verify-server-cert": "サーバの証明書を確認",
"compatmode": "旧MQTT 3.1のサポート" "compatmode": "旧MQTT 3.1のサポート"
}, },
"sections-label":{
"birth-message": "接続時の送信メッセージ(Birthメッセージ)",
"will-message":"予期しない切断時の送信メッセージ(Willメッセージ)",
"close-message":"切断前の送信メッセージ(Closeメッセージ)"
},
"tabs-label": { "tabs-label": {
"connection": "接続", "connection": "接続",
"security": "セキュリティ", "security": "セキュリティ",
"will": "Willメッセージ", "messages": "メッセージ"
"birth": "Birthメッセージ"
}, },
"placeholder": { "placeholder": {
"clientid": "IDを自動生成する場合は、無記入にしてください", "clientid": "IDを自動生成する場合は、無記入にしてください",
"clientid-nonclean": "新規ではないセッションを設定してください", "clientid-nonclean": "新規ではないセッションを設定してください",
"will-topic": "Willメッセージを無効化する場合は、無記入にしてください", "will-topic": "Willメッセージを無効化する場合は、無記入にしてください",
"birth-topic": "Birthメッセージを無効化する場合は、無記入にしてください" "birth-topic": "Birthメッセージを無効化する場合は、無記入にしてください",
"close-topic": "Closeメッセージを無効化する場合は、無記入にしてください"
}, },
"state": { "state": {
"connected": "ブローカへ接続しました: __broker__", "connected": "ブローカへ接続しました: __broker__",
@ -488,14 +493,14 @@
"output": "出力", "output": "出力",
"group": "グループ", "group": "グループ",
"interface": "ローカルIP", "interface": "ローカルIP",
"interfaceprompt": "(任意) 使用するローカルIPアドレス",
"send": "送信", "send": "送信",
"toport": "ポート", "toport": "ポート",
"address": "アドレス", "address": "アドレス",
"decode-base64": "Base64形式のペイロードを復号" "decode-base64": "Base64形式のペイロードを復号"
}, },
"placeholder": { "placeholder": {
"interface": "(任意) eth0のIPアドレス", "interface": "(任意) 使用するローカルインターフェイスもしくはアドレス",
"interfaceprompt": "(任意) 使用するローカルインターフェイスもしくはアドレス",
"address": "宛先IPアドレス" "address": "宛先IPアドレス"
}, },
"udpmsgs": "UDPメッセージ", "udpmsgs": "UDPメッセージ",
@ -523,10 +528,11 @@
"mc-group": "udpードがグループ __group__ へマルチキャストしました", "mc-group": "udpードがグループ __group__ へマルチキャストしました",
"listener-stopped": "udpードが待ち受けを停止しました", "listener-stopped": "udpードが待ち受けを停止しました",
"output-stopped": "udpードが出力を停止しました", "output-stopped": "udpードが出力を停止しました",
"mc-ready": "udpードはマルチキャストの準備ができています: __outport__ -> __host__:__port__", "mc-ready": "udpードはマルチキャストの準備ができています: __iface__:__outport__ -> __host__:__port__",
"bc-ready": "udpードはブロードキャストの準備ができています: __outport__ -> __host__:__port__", "bc-ready": "udpードはブロードキャストの準備ができています: __outport__ -> __host__:__port__",
"ready": "udpードは準備ができています: __outport__ -> __host__:__port__", "ready": "udpードは準備ができています: __outport__ -> __host__:__port__",
"ready-nolocal": "udpードは準備ができています: __host__:__port__" "ready-nolocal": "udpードは準備ができています: __host__:__port__",
"re-use": "udp再利用ソケット: __outport__ -> __host__:__port__"
}, },
"errors": { "errors": {
"access-error": "UDP接続エラー 管理者権限で1024未満のポート番号にアクセスできる必要があります", "access-error": "UDP接続エラー 管理者権限で1024未満のポート番号にアクセスできる必要があります",
@ -536,6 +542,8 @@
"ip-notset": "udp: IPアドレスが設定されていません", "ip-notset": "udp: IPアドレスが設定されていません",
"port-notset": "udp: ポートが設定されていません", "port-notset": "udp: ポートが設定されていません",
"port-invalid": "udp: ポート番号が不正です", "port-invalid": "udp: ポート番号が不正です",
"alreadyused": "udp: 既に__port__番ポートが使用されています",
"ifnotfound": "udp: インターフェイス __iface__ がありません",
"alreadyused": "udp: 既にポートが使用されています" "alreadyused": "udp: 既にポートが使用されています"
} }
}, },
@ -661,7 +669,8 @@
"html": { "html": {
"label": { "label": {
"select": "抽出する要素", "select": "抽出する要素",
"output": "出力" "output": "出力",
"in": "対象:"
}, },
"output": { "output": {
"html": "要素内のHTML", "html": "要素内のHTML",
@ -761,7 +770,9 @@
"status": { "status": {
"stopped": "停止", "stopped": "停止",
"closed": "切断", "closed": "切断",
"not-running": "停止中" "not-running": "停止中",
"not-available": "利用不可",
"na": "N/A : __value__"
}, },
"errors": { "errors": {
"ignorenode": "Raspberry Pi固有のードを無視しました", "ignorenode": "Raspberry Pi固有のードを無視しました",

View File

@ -525,7 +525,8 @@
"mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__", "mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__",
"bc-ready": "udp 广播已准备好: __outport__ -> __host__:__port__", "bc-ready": "udp 广播已准备好: __outport__ -> __host__:__port__",
"ready": "udp 已准备好: __outport__ -> __host__:__port__", "ready": "udp 已准备好: __outport__ -> __host__:__port__",
"ready-nolocal": "udp 已准备好: __host__:__port__" "ready-nolocal": "udp 已准备好: __host__:__port__",
"re-use": "udp 重用套接字: __outport__ -> __host__:__port__"
}, },
"errors": { "errors": {
"access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口", "access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口",

View File

@ -85,6 +85,7 @@
{v:"false",t:"switch.rules.false",kind:'V'}, {v:"false",t:"switch.rules.false",kind:'V'},
{v:"null",t:"switch.rules.null",kind:'V'}, {v:"null",t:"switch.rules.null",kind:'V'},
{v:"nnull",t:"switch.rules.nnull",kind:'V'}, {v:"nnull",t:"switch.rules.nnull",kind:'V'},
{v:"istype",t:"switch.rules.istype",kind:'V'},
{v:"head",t:"switch.rules.head",kind:'S'}, {v:"head",t:"switch.rules.head",kind:'S'},
{v:"index",t:"switch.rules.index",kind:'S'}, {v:"index",t:"switch.rules.index",kind:'S'},
{v:"tail",t:"switch.rules.tail",kind:'S'}, {v:"tail",t:"switch.rules.tail",kind:'S'},
@ -160,6 +161,7 @@
var selectField = rule.find("select"); var selectField = rule.find("select");
var type = selectField.val()||""; var type = selectField.val()||"";
var valueField = rule.find(".node-input-rule-value"); var valueField = rule.find(".node-input-rule-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 btwnField1 = rule.find(".node-input-rule-btwn-value"); var btwnField1 = rule.find(".node-input-rule-btwn-value");
@ -180,6 +182,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 === "istype") {
typeField.typedInput("width",(newWidth-selectWidth-70));
} else { } else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") { if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
// valueField.hide(); // valueField.hide();
@ -232,6 +236,18 @@
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',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',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',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',previousValueType]});
var typeValueField = $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row)
.typedInput({default:'string',types:[
{value:"string",label:"string",hasValue:false},
{value:"number",label:"number",hasValue:false},
{value:"boolean",label:"boolean",hasValue:false},
{value:"array",label:"array",hasValue:false},
{value:"buffer",label:"buffer",hasValue:false},
{value:"object",label:"object",hasValue:false},
{value:"json",label:"JSON string",hasValue:false},
{value:"undefined",label:"undefined",hasValue:false},
{value:"null",label:"null",hasValue:false}
]});
var finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row); var finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row);
finalspan.append(' &#8594; <span class="node-input-rule-index">'+(i+1)+'</span> '); finalspan.append(' &#8594; <span class="node-input-rule-index">'+(i+1)+'</span> ');
var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2); var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);
@ -243,26 +259,39 @@
valueField.typedInput('hide'); valueField.typedInput('hide');
expValueField.typedInput('hide'); expValueField.typedInput('hide');
numValueField.typedInput('hide'); numValueField.typedInput('hide');
typeValueField.typedInput('hide');
btwnValueField.typedInput('show'); btwnValueField.typedInput('show');
} else if ((type === "head") || (type === "tail")) { } else if ((type === "head") || (type === "tail")) {
btwnValueField.typedInput('hide'); btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide'); btwnValue2Field.typedInput('hide');
expValueField.typedInput('hide'); expValueField.typedInput('hide');
numValueField.typedInput('show'); numValueField.typedInput('show');
typeValueField.typedInput('hide');
valueField.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');
numValueField.typedInput('hide'); numValueField.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');
numValueField.typedInput('hide'); numValueField.typedInput('hide');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") { if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
valueField.typedInput('hide'); valueField.typedInput('hide');
} else { typeValueField.typedInput('hide');
}
else
if (type === "istype") {
valueField.typedInput('hide');
typeValueField.typedInput('show');
}
else {
typeValueField.typedInput('hide');
valueField.typedInput('show'); valueField.typedInput('show');
} }
} }
@ -287,6 +316,9 @@
} else if ((rule.t === "head") || (rule.t === "tail")) { } else if ((rule.t === "head") || (rule.t === "tail")) {
numValueField.typedInput('value',rule.v); numValueField.typedInput('value',rule.v);
numValueField.typedInput('type',rule.vt||'num'); numValueField.typedInput('type',rule.vt||'num');
} else if (rule.t === "istype") {
typeValueField.typedInput('value',rule.vt);
typeValueField.typedInput('type',rule.vt);
} else if (rule.t === "jsonata_exp") { } 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');
@ -359,6 +391,9 @@
} else if ((type === "head") || (type === "tail")) { } else if ((type === "head") || (type === "tail")) {
r.v = rule.find(".node-input-rule-num-value").typedInput('value'); r.v = rule.find(".node-input-rule-num-value").typedInput('value');
r.vt = rule.find(".node-input-rule-num-value").typedInput('type'); r.vt = rule.find(".node-input-rule-num-value").typedInput('type');
} else if (type === "istype") {
r.v = rule.find(".node-input-rule-type-value").typedInput('type');
r.vt = rule.find(".node-input-rule-type-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

@ -31,6 +31,16 @@ module.exports = function(RED) {
'false': function(a) { return a === false; }, 'false': function(a) { return a === false; },
'null': function(a) { return (typeof a == "undefined" || a === null); }, 'null': function(a) { return (typeof a == "undefined" || a === null); },
'nnull': function(a) { return (typeof a != "undefined" && a !== null); }, 'nnull': function(a) { return (typeof a != "undefined" && a !== null); },
'istype': function(a, b) {
if (b === "array") { return Array.isArray(a); }
else if (b === "buffer") { return Buffer.isBuffer(a); }
else if (b === "json") {
try { JSON.parse(a); return true; } // or maybe ??? a !== null; }
catch(e) { return false;}
}
else if (b === "null") { return a === null; }
else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; }
},
'head': function(a, b, c, d, parts) { 'head': function(a, b, c, d, parts) {
var count = Number(b); var count = Number(b);
return (parts.index < count); return (parts.index < count);
@ -292,6 +302,10 @@ module.exports = function(RED) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message})); node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return; return;
} }
} else if (rule.vt === 'json') {
v1 = "json";
} else if (rule.vt === 'null') {
v1 = "null";
} else { } else {
try { try {
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg); v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);

View File

@ -2,7 +2,7 @@
<script type="text/x-red" data-template-name="html"> <script type="text/x-red" data-template-name="html">
<div class="form-row"> <div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label> <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/> <input type="text" id="node-input-property" style="width:70%">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-tag"><i class="fa fa-filter"></i> <span data-i18n="html.label.select"></span></label> <label for="node-input-tag"><i class="fa fa-filter"></i> <span data-i18n="html.label.select"></span></label>
@ -24,6 +24,10 @@
<option value="multi" data-i18n="html.format.multi"></option> <option value="multi" data-i18n="html.format.multi"></option>
</select> </select>
</div> </div>
<div class="form-row">
<label for="node-input-outproperty">&nbsp;</label>
<span data-i18n="html.label.in" style="padding-left:8px; padding-right:2px; vertical-align:-1px;"></span> <input type="text" id="node-input-outproperty" style="width:64%">
</div>
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@ -59,6 +63,7 @@
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: {value:"payload"}, property: {value:"payload"},
outproperty: {value:"payload"},
tag: {value:""}, tag: {value:""},
ret: {value:"html"}, ret: {value:"html"},
as: {value:"single"} as: {value:"single"}
@ -74,6 +79,7 @@
}, },
oneditprepare: function() { oneditprepare: function() {
$("#node-input-property").typedInput({default:'msg',types:['msg']}); $("#node-input-property").typedInput({default:'msg',types:['msg']});
$("#node-input-outproperty").typedInput({default:'msg',types:['msg']});
} }
}); });
</script> </script>

View File

@ -21,6 +21,7 @@ module.exports = function(RED) {
function CheerioNode(n) { function CheerioNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.property = n.property||"payload"; this.property = n.property||"payload";
this.outproperty = n.outproperty||this.property||"payload";
this.tag = n.tag; this.tag = n.tag;
this.ret = n.ret || "html"; this.ret = n.ret || "html";
this.as = n.as || "single"; this.as = n.as || "single";
@ -48,7 +49,7 @@ module.exports = function(RED) {
/* istanbul ignore else */ /* istanbul ignore else */
if (pay2) { if (pay2) {
var new_msg = RED.util.cloneMessage(msg); var new_msg = RED.util.cloneMessage(msg);
RED.util.setMessageProperty(new_msg,node.property,pay2); RED.util.setMessageProperty(new_msg,node.outproperty,pay2);
new_msg.parts = { new_msg.parts = {
id: msg._msgid, id: msg._msgid,
index: index, index: index,
@ -68,7 +69,7 @@ module.exports = function(RED) {
index++; index++;
}); });
if (node.as === "single") { // Always return an array - even if blank if (node.as === "single") { // Always return an array - even if blank
RED.util.setMessageProperty(msg,node.property,pay); RED.util.setMessageProperty(msg,node.outproperty,pay);
node.send(msg); node.send(msg);
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "0.18.4", "version": "0.18.5",
"description": "A visual tool for wiring the Internet of Things", "description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -43,22 +43,22 @@
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"cors": "2.8.4", "cors": "2.8.4",
"cron": "1.3.0", "cron": "1.3.0",
"express": "4.16.2", "express": "4.16.3",
"express-session": "1.15.6", "express-session": "1.15.6",
"follow-redirects": "1.3.0", "follow-redirects": "1.4.1",
"fs-extra": "5.0.0", "fs-extra": "5.0.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "1.0.2", "hash-sum": "1.0.2",
"i18next": "1.10.6", "i18next": "1.10.6",
"ink-docstrap": "^1.3.2", "ink-docstrap": "^1.3.2",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.10.0", "js-yaml": "3.11.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.5.0", "jsonata": "1.5.3",
"media-typer": "0.3.0", "media-typer": "0.3.0",
"memorystore": "1.6.0", "memorystore": "1.6.0",
"mime": "1.4.1", "mime": "1.4.1",
"mqtt": "2.15.1", "mqtt": "2.17.0",
"multer": "1.3.0", "multer": "1.3.0",
"mustache": "2.3.0", "mustache": "2.3.0",
"node-red-node-email": "0.1.*", "node-red-node-email": "0.1.*",
@ -71,10 +71,10 @@
"passport": "0.4.0", "passport": "0.4.0",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.3.2", "raw-body": "2.3.3",
"semver": "5.4.1", "semver": "5.5.0",
"sentiment": "2.1.0", "sentiment": "2.1.0",
"uglify-js": "3.3.6", "uglify-js": "3.3.24",
"when": "3.7.8", "when": "3.7.8",
"ws": "1.1.5", "ws": "1.1.5",
"xml2js": "0.4.19" "xml2js": "0.4.19"
@ -103,6 +103,7 @@
"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", "grunt-webdriver": "^2.0.3",
"http-proxy": "^1.16.2",
"ink-docstrap": "^1.3.2", "ink-docstrap": "^1.3.2",
"istanbul": "0.4.5", "istanbul": "0.4.5",
"mocha": "^5.1.1", "mocha": "^5.1.1",
@ -113,7 +114,8 @@
"wdio-chromedriver-service": "^0.1.1", "wdio-chromedriver-service": "^0.1.1",
"wdio-mocha-framework": "^0.5.11", "wdio-mocha-framework": "^0.5.11",
"wdio-spec-reporter": "^0.1.3", "wdio-spec-reporter": "^0.1.3",
"webdriverio": "^4.9.11" "webdriverio": "^4.9.11",
"node-red-node-test-helper": "^0.1.7"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=4"

4
red.js
View File

@ -192,7 +192,7 @@ try {
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 v4 or later");
} else if (err.code == "not_built") { } else if (err.code == "not_built") {
console.log("Node-RED has not been built. See README.md for details"); 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:");
@ -276,7 +276,7 @@ function getListenPath() {
} }
var listenPath = 'http'+(settings.https?'s':'')+'://'+ var listenPath = 'http'+(settings.https?'s':'')+'://'+
(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost)+ (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+
':'+port; ':'+port;
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
listenPath += settings.httpAdminRoot; listenPath += settings.httpAdminRoot;

View File

@ -93,6 +93,7 @@
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>", "credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
"credentials_load_failed_reset":"<p>Credentials could not be decrypted</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p><p>The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.</p>", "credentials_load_failed_reset":"<p>Credentials could not be decrypted</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p><p>The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.</p>",
"missing_flow_file": "<p>Project flow file not found.</p><p>The project is not configured with a flow file.</p>", "missing_flow_file": "<p>Project flow file not found.</p><p>The project is not configured with a flow file.</p>",
"missing_package_file": "<p>Project package file not found.</p><p>The project is missing a package.json file.</p>",
"project_empty": "<p>The project is empty.</p><p>Do you want to create a default set of project files?<br/>Otherwise, you will have to manually add files to the project outside of the editor.</p>", "project_empty": "<p>The project is empty.</p><p>Do you want to create a default set of project files?<br/>Otherwise, you will have to manually add files to the project outside of the editor.</p>",
"project_not_found": "<p>Project '__project__' not found.</p>", "project_not_found": "<p>Project '__project__' not found.</p>",
"git_merge_conflict": "<p>Automatic merging of changes failed.</p><p>Fix the unmerged conflicts then commit the results.</p>" "git_merge_conflict": "<p>Automatic merging of changes failed.</p><p>Fix the unmerged conflicts then commit the results.</p>"

View File

@ -68,10 +68,17 @@ var api = module.exports = {
try { try {
var safeSettings = { var safeSettings = {
httpNodeRoot: runtime.settings.httpNodeRoot||"/", httpNodeRoot: runtime.settings.httpNodeRoot||"/",
version: runtime.settings.version, version: runtime.settings.version
user: opts.user }
if (opts.user) {
safeSettings.user = {}
var props = ["anonymous","username","image","permissions"];
props.forEach(prop => {
if (opts.user.hasOwnProperty(prop)) {
safeSettings.user[prop] = opts.user[prop];
}
})
} }
if (util.isArray(runtime.settings.paletteCategories)) { if (util.isArray(runtime.settings.paletteCategories)) {
safeSettings.paletteCategories = runtime.settings.paletteCategories; safeSettings.paletteCategories = runtime.settings.paletteCategories;
} }

View File

@ -237,8 +237,8 @@ function Flow(global,flow) {
this.handleError = function(node,logMessage,msg) { this.handleError = function(node,logMessage,msg) {
var count = 1; var count = 1;
if (msg && msg.hasOwnProperty("error")) { if (msg && msg.hasOwnProperty("error") && msg.error !== null) {
if (msg.error.hasOwnProperty("source")) { if (msg.error.hasOwnProperty("source") && msg.error.source !== null) {
if (msg.error.source.id === node.id) { if (msg.error.source.id === node.id) {
count = msg.error.source.count+1; count = msg.error.source.count+1;
if (count === 10) { if (count === 10) {

View File

@ -53,15 +53,13 @@ function getGitUser(user) {
} }
return null; return null;
} }
function Project(name) { function Project(path) {
this.name = name; this.path = path;
this.path = fspath.join(projectsDir,name); this.name = fspath.basename(path);
this.paths = {}; this.paths = {};
this.files = {}; this.files = {};
this.auth = {origin:{}}; this.auth = {origin:{}};
this.missingFiles = []; this.missingFiles = [];
this.credentialSecret = null; this.credentialSecret = null;
} }
Project.prototype.load = function () { Project.prototype.load = function () {
@ -70,7 +68,9 @@ Project.prototype.load = function () {
// console.log(globalProjectSettings) // console.log(globalProjectSettings)
var projectSettings = {}; var projectSettings = {};
if (globalProjectSettings) { if (globalProjectSettings) {
projectSettings = globalProjectSettings.projects[this.name]||{}; if (globalProjectSettings.projects.hasOwnProperty(this.name)) {
projectSettings = globalProjectSettings.projects[this.name] || {};
}
} }
this.credentialSecret = projectSettings.credentialSecret; this.credentialSecret = projectSettings.credentialSecret;
@ -81,9 +81,7 @@ Project.prototype.load = function () {
var promises = []; var promises = [];
return checkProjectFiles(project).then(function(missingFiles) { return checkProjectFiles(project).then(function(missingFiles) {
if (missingFiles.length > 0) { project.missingFiles = missingFiles;
project.missingFiles = missingFiles;
}
if (missingFiles.indexOf('package.json') === -1) { if (missingFiles.indexOf('package.json') === -1) {
project.paths['package.json'] = fspath.join(project.path,"package.json"); project.paths['package.json'] = fspath.join(project.path,"package.json");
promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) { promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) {
@ -135,9 +133,9 @@ Project.prototype.load = function () {
Project.prototype.initialise = function(user,data) { Project.prototype.initialise = function(user,data) {
var project = this; var project = this;
if (!this.empty) { // if (!this.empty) {
throw new Error("Cannot initialise non-empty project"); // throw new Error("Cannot initialise non-empty project");
} // }
var files = Object.keys(defaultFileSet); var files = Object.keys(defaultFileSet);
var promises = []; var promises = [];
@ -148,17 +146,25 @@ Project.prototype.initialise = function(user,data) {
promises.push(settings.set('projects',projects)); promises.push(settings.set('projects',projects));
} }
project.files.flow = data.files.flow; if (data.hasOwnProperty('files')) {
project.files.credentials = data.files.credentials; if (data.files.hasOwnProperty('flow') && data.files.hasOwnProperty('credentials')) {
var flowFilePath = fspath.join(project.path,project.files.flow); project.files.flow = data.files.flow;
var credsFilePath = getCredentialsFilename(flowFilePath); project.files.credentials = data.files.credentials;
promises.push(util.writeFile(flowFilePath,"[]")); var flowFilePath = fspath.join(project.path,project.files.flow);
promises.push(util.writeFile(credsFilePath,"{}")); var credsFilePath = getCredentialsFilename(flowFilePath);
files.push(project.files.flow); promises.push(util.writeFile(flowFilePath,"[]"));
files.push(project.files.credentials); promises.push(util.writeFile(credsFilePath,"{}"));
files.push(project.files.flow);
files.push(project.files.credentials);
}
}
for (var file in defaultFileSet) { for (var file in defaultFileSet) {
if (defaultFileSet.hasOwnProperty(file)) { if (defaultFileSet.hasOwnProperty(file)) {
promises.push(util.writeFile(fspath.join(project.path,file),defaultFileSet[file](project))); var path = fspath.join(project.path,file);
if (!fs.existsSync(path)) {
promises.push(util.writeFile(path,defaultFileSet[file](project)));
}
} }
} }
@ -740,7 +746,7 @@ Project.prototype.getCredentialsFileBackup = function() {
return getBackupFilename(this.getCredentialsFile()); return getBackupFilename(this.getCredentialsFile());
} }
Project.prototype.toJSON = function () { Project.prototype.export = function () {
return { return {
name: this.name, name: this.name,
@ -780,28 +786,18 @@ function getBackupFilename(filename) {
return fspath.join(ffDir,"."+ffName+".backup"); return fspath.join(ffDir,"."+ffName+".backup");
} }
function checkProjectExists(project) { function checkProjectExists(projectPath) {
var projectPath = fspath.join(projectsDir,project);
return fs.pathExists(projectPath).then(function(exists) { return fs.pathExists(projectPath).then(function(exists) {
if (!exists) { if (!exists) {
var e = new Error("Project not found: "+project); var e = new Error("Project not found");
e.code = "project_not_found"; e.code = "project_not_found";
e.project = project; var name = fspath.basename(projectPath);
e.project = name;
throw e; throw e;
} }
}); });
} }
function createProjectDirectory(project) {
var projectPath = fspath.join(projectsDir,project);
return fs.ensureDir(projectPath);
}
function deleteProjectDirectory(project) {
var projectPath = fspath.join(projectsDir,project);
return fs.remove(projectPath);
}
function createDefaultProject(user, project) { function createDefaultProject(user, project) {
var projectPath = fspath.join(projectsDir,project.name); var projectPath = fspath.join(projectsDir,project.name);
// Create a basic skeleton of a project // Create a basic skeleton of a project
@ -904,17 +900,23 @@ function createProject(user, metadata) {
} else { } else {
username = user.username; username = user.username;
} }
if (!metadata.path) {
throw new Error("Project missing path property");
}
if (!metadata.name) {
throw new Error("Project missing name property");
}
var project = metadata.name; var project = metadata.name;
var projectPath = metadata.path;
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
var projectPath = fspath.join(projectsDir,project);
fs.stat(projectPath, function(err,stat) { fs.stat(projectPath, function(err,stat) {
if (!err) { if (!err) {
var e = new Error("NLS: Project already exists"); var e = new Error("NLS: Project already exists");
e.code = "project_exists"; e.code = "project_exists";
return reject(e); return reject(e);
} }
createProjectDirectory(project).then(function() { fs.ensureDir(projectPath).then(function() {
var projects = settings.get('projects'); var projects = settings.get('projects');
if (!projects) { if (!projects) {
projects = { projects = {
@ -951,7 +953,7 @@ function createProject(user, metadata) {
return createDefaultProject(user, metadata); return createDefaultProject(user, metadata);
} }
}).then(function() { }).then(function() {
resolve(getProject(project)) resolve(loadProject(projectPath))
}).catch(function(err) { }).catch(function(err) {
fs.remove(projectPath,function() { fs.remove(projectPath,function() {
reject(err); reject(err);
@ -961,50 +963,21 @@ function createProject(user, metadata) {
}) })
} }
function deleteProject(user, name) { function deleteProject(user, projectPath) {
return checkProjectExists(name).then(function() { return checkProjectExists(projectPath).then(function() {
if (currentProject && currentProject.name === name) { return fs.remove(projectPath).then(function() {
var e = new Error("NLS: Can't delete the active project"); var name = fspath.basename(projectPath);
e.code = "cannot_delete_active_project"; var projects = settings.get('projects');
throw e; delete projects.projects[name];
} return settings.set('projects', projects);
else {
return deleteProjectDirectory(name).then(function() {
var projects = settings.get('projects');
delete projects.projects[name];
return settings.set('projects', projects);
});
}
});
}
var currentProject;
function getProject(name) {
return checkProjectExists(name).then(function() {
if (currentProject && currentProject.name === name) {
return currentProject;
}
currentProject = new Project(name);
return currentProject.load();
});
}
function listProjects() {
return fs.readdir(projectsDir).then(function(fns) {
var dirs = [];
fns.sort(function(A,B) {
return A.toLowerCase().localeCompare(B.toLowerCase());
}).filter(function(fn) {
var fullPath = fspath.join(projectsDir,fn);
if (fn[0] != ".") {
var stats = fs.lstatSync(fullPath);
if (stats.isDirectory()) {
dirs.push(fn);
}
}
}); });
return dirs; });
}
function loadProject(projectPath) {
return checkProjectExists(projectPath).then(function() {
var project = new Project(projectPath);
return project.load();
}); });
} }
@ -1018,9 +991,7 @@ function init(_settings, _runtime) {
module.exports = { module.exports = {
init: init, init: init,
get: getProject, load: loadProject,
create: createProject, create: createProject,
delete: deleteProject, delete: deleteProject
list: listProjects
} }

View File

@ -48,7 +48,9 @@ function runGitCommand(args,cwd,env) {
var err = new Error(stderr); var err = new Error(stderr);
err.stdout = stdout; err.stdout = stdout;
err.stderr = stderr; err.stderr = stderr;
if (/fatal: could not read/i.test(stderr)) { if(/Connection refused/i.test(stderr)) {
err.code = "git_connection_failed";
} else if (/fatal: could not read/i.test(stderr)) {
// Username/Password // Username/Password
err.code = "git_auth_failed"; err.code = "git_auth_failed";
} else if(/HTTP Basic: Access denied/i.test(stderr)) { } else if(/HTTP Basic: Access denied/i.test(stderr)) {
@ -58,8 +60,6 @@ function runGitCommand(args,cwd,env) {
} else if(/Host key verification failed/i.test(stderr)) { } else if(/Host key verification failed/i.test(stderr)) {
// TODO: handle host key verification errors separately // TODO: handle host key verification errors separately
err.code = "git_auth_failed"; err.code = "git_auth_failed";
} else if(/Connection refused/i.test(stderr)) {
err.code = "git_connection_failed";
} else if (/commit your changes or stash/i.test(stderr)) { } else if (/commit your changes or stash/i.test(stderr)) {
err.code = "git_local_overwrite"; err.code = "git_local_overwrite";
} else if (/CONFLICT/.test(err.stdout)) { } else if (/CONFLICT/.test(err.stdout)) {

View File

@ -127,10 +127,20 @@ function init(_settings, _runtime) {
activeProject = globalSettings.projects.activeProject; activeProject = globalSettings.projects.activeProject;
} }
if (settings.flowFile) { if (settings.flowFile) {
// if flowFile is a known project name - use it
if (globalSettings.projects.projects.hasOwnProperty(settings.flowFile)) { if (globalSettings.projects.projects.hasOwnProperty(settings.flowFile)) {
activeProject = settings.flowFile; activeProject = settings.flowFile;
globalSettings.projects.activeProject = settings.flowFile; globalSettings.projects.activeProject = settings.flowFile;
saveSettings = true; saveSettings = true;
} else {
// if it resolves to a dir - use it... but:
// - where to get credsecret from?
// - what if the name clashes with a known project?
// var stat = fs.statSync(settings.flowFile);
// if (stat && stat.isDirectory()) {
// activeProject = settings.flowFile;
// }
} }
} }
if (!activeProject) { if (!activeProject) {
@ -148,6 +158,24 @@ function init(_settings, _runtime) {
return Promise.resolve(); return Promise.resolve();
} }
function listProjects() {
return fs.readdir(projectsDir).then(function(fns) {
var dirs = [];
fns.sort(function(A,B) {
return A.toLowerCase().localeCompare(B.toLowerCase());
}).filter(function(fn) {
var fullPath = fspath.join(projectsDir,fn);
if (fn[0] != ".") {
var stats = fs.lstatSync(fullPath);
if (stats.isDirectory()) {
dirs.push(fn);
}
}
});
return dirs;
});
}
function getUserGitSettings(user) { function getUserGitSettings(user) {
var userSettings = settings.getUserSettings(user)||{}; var userSettings = settings.getUserSettings(user)||{};
return userSettings.git; return userSettings.git;
@ -160,7 +188,11 @@ function getBackupFilename(filename) {
} }
function loadProject(name) { function loadProject(name) {
return Projects.get(name).then(function(project) { var projectPath = name;
if (projectPath.indexOf(fspath.sep) === -1) {
projectPath = fspath.join(projectsDir,name);
}
return Projects.load(projectPath).then(function(project) {
activeProject = project; activeProject = project;
flowsFullPath = project.getFlowFile(); flowsFullPath = project.getFlowFile();
flowsFileBackup = project.getFlowFileBackup(); flowsFileBackup = project.getFlowFileBackup();
@ -170,26 +202,20 @@ function loadProject(name) {
}) })
} }
function listProjects(user) {
return Projects.list();
}
function getProject(user, name) { function getProject(user, name) {
checkActiveProject(name); checkActiveProject(name);
//return when.resolve(activeProject.info); //return when.resolve(activeProject.info);
var username; return Promise.resolve(activeProject.export());
if (!user) {
username = "_";
} else {
username = user.username;
}
return Projects.get(name).then(function(project) {
return project.toJSON();
});
} }
function deleteProject(user, name) { function deleteProject(user, name) {
return Projects.delete(user, name); if (activeProject && activeProject.name === name) {
var e = new Error("NLS: Can't delete the active project");
e.code = "cannot_delete_active_project";
throw e;
}
var projectPath = fspath.join(projectsDir,name);
return Projects.delete(user, projectPath);
} }
function checkActiveProject(project) { function checkActiveProject(project) {
@ -347,6 +373,7 @@ function createProject(user, metadata) {
metadata.files.oldCredentials = credentialsFile; metadata.files.oldCredentials = credentialsFile;
metadata.files.credentialSecret = currentEncryptionKey; metadata.files.credentialSecret = currentEncryptionKey;
} }
metadata.path = fspath.join(projectsDir,metadata.name);
return Projects.create(user, metadata).then(function(p) { return Projects.create(user, metadata).then(function(p) {
return setActiveProject(user, p.name); return setActiveProject(user, p.name);
}).then(function() { }).then(function() {
@ -479,6 +506,12 @@ function getFlows() {
error.code = "project_empty"; error.code = "project_empty";
return when.reject(error); return when.reject(error);
} }
if (activeProject.missingFiles && activeProject.missingFiles.indexOf('package.json') !== -1) {
log.warn("Project missing package.json");
error = new Error("Project missing package.json");
error.code = "missing_package_file";
return when.reject(error);
}
if (!activeProject.getFlowFile()) { if (!activeProject.getFlowFile()) {
log.warn("Project has no flow file"); log.warn("Project has no flow file");
error = new Error("Project has no flow file"); error = new Error("Project has no flow file");

View File

@ -80,27 +80,26 @@ module.exports = {
*/ */
writeFile: function(path,content,backupPath) { writeFile: function(path,content,backupPath) {
if (backupPath) { if (backupPath) {
try { if (fs.existsSync(path)) {
fs.renameSync(path,backupPath); fs.renameSync(path,backupPath);
} catch(err) { }
} }
} return when.promise(function(resolve,reject) {
return when.promise(function(resolve,reject) { var stream = fs.createWriteStream(path);
var stream = fs.createWriteStream(path); stream.on('open',function(fd) {
stream.on('open',function(fd) { stream.write(content,'utf8',function() {
stream.write(content,'utf8',function() { fs.fsync(fd,function(err) {
fs.fsync(fd,function(err) { if (err) {
if (err) { log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()}));
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()})); }
} stream.end(resolve);
stream.end(resolve); });
}); });
}); });
}); stream.on('error',function(err) {
stream.on('error',function(err) { reject(err);
reject(err); });
}); });
});
}, },
readFile: readFile, readFile: readFile,

View File

@ -23,6 +23,7 @@ module.exports = {
uiPort: process.env.PORT || 1880, uiPort: process.env.PORT || 1880,
// By default, the Node-RED UI accepts connections on all IPv4 interfaces. // By default, the Node-RED UI accepts connections on all IPv4 interfaces.
// To listen on all IPv6 addresses, set uiHost to "::",
// The following property can be used to listen on a specific interface. For // The following property can be used to listen on a specific interface. For
// example, the following would only allow connections from the local machine. // example, the following would only allow connections from the local machine.
//uiHost: "127.0.0.1", //uiHost: "127.0.0.1",

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var sentimentNode = require("../../../../nodes/core/analysis/72-sentiment.js"); var sentimentNode = require("../../../../nodes/core/analysis/72-sentiment.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('sentiment Node', function() { describe('sentiment Node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var injectNode = require("../../../../nodes/core/core/20-inject.js"); var injectNode = require("../../../../nodes/core/core/20-inject.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('inject node', function() { describe('inject node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var catchNode = require("../../../../nodes/core/core/25-catch.js"); var catchNode = require("../../../../nodes/core/core/25-catch.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('catch Node', function() { describe('catch Node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var catchNode = require("../../../../nodes/core/core/25-status.js"); var catchNode = require("../../../../nodes/core/core/25-status.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('status Node', function() { describe('status Node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var debugNode = require("../../../../nodes/core/core/58-debug.js"); var debugNode = require("../../../../nodes/core/core/58-debug.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var WebSocket = require('ws'); var WebSocket = require('ws');
describe('debug node', function() { describe('debug node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var linkNode = require("../../../../nodes/core/core/60-link.js"); var linkNode = require("../../../../nodes/core/core/60-link.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('link Node', function() { describe('link Node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var sinon = require("sinon"); var sinon = require("sinon");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var execNode = require("../../../../nodes/core/core/75-exec.js"); var execNode = require("../../../../nodes/core/core/75-exec.js");
var osType = require("os").type(); var osType = require("os").type();

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var functionNode = require("../../../../nodes/core/core/80-function.js"); var functionNode = require("../../../../nodes/core/core/80-function.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('function node', function() { describe('function node', function() {
@ -242,7 +242,7 @@ describe('function node', function() {
}); });
it('should handle and log script error', function(done) { it('should handle and log script error', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"retunr"}]; var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"var a = 1;\nretunr"}];
helper.load(functionNode, flow, function() { helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
@ -256,7 +256,7 @@ describe('function node', function() {
msg.should.have.property('level', helper.log().ERROR); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'n1'); msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function'); msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 1, col 1)'); msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)');
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var templateNode = require("../../../../nodes/core/core/80-template.js"); var templateNode = require("../../../../nodes/core/core/80-template.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('template node', function() { describe('template node', function() {

View File

@ -17,7 +17,7 @@
var should = require("should"); var should = require("should");
var delayNode = require("../../../../nodes/core/core/89-delay.js"); var delayNode = require("../../../../nodes/core/core/89-delay.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var GRACE_PERCENTAGE=10; var GRACE_PERCENTAGE=10;

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var sinon = require("sinon"); var sinon = require("sinon");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var triggerNode = require("../../../../nodes/core/core/89-trigger.js"); var triggerNode = require("../../../../nodes/core/core/89-trigger.js");
var RED = require("../../../../red/red.js"); var RED = require("../../../../red/red.js");
@ -419,7 +419,7 @@ describe('trigger node', function() {
}); });
it('should be able to extend the delay (but with no 2nd output)', function(done) { it('should be able to extend the delay (but with no 2nd output)', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"50", wires:[["n2"]] }, var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"100", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() { helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -434,7 +434,7 @@ describe('trigger node', function() {
else { else {
msg.should.have.a.property("payload", "World"); msg.should.have.a.property("payload", "World");
//console.log(Date.now() - ss); //console.log(Date.now() - ss);
(Date.now() - ss).should.be.greaterThan(70); (Date.now() - ss).should.be.greaterThan(140);
done(); done();
} }
} }
@ -447,7 +447,7 @@ describe('trigger node', function() {
},20); },20);
setTimeout( function() { setTimeout( function() {
n1.emit("input", {payload:"World"}); n1.emit("input", {payload:"World"});
},80); },150);
}); });
}); });

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var commentNode = require("../../../../nodes/core/core/90-comment.js"); var commentNode = require("../../../../nodes/core/core/90-comment.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('comment Node', function() { describe('comment Node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var unknown = require("../../../../nodes/core/core/98-unknown.js"); var unknown = require("../../../../nodes/core/core/98-unknown.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('unknown Node', function() { describe('unknown Node', function() {

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
var ws = require("ws"); var ws = require("ws");
var when = require("when"); var when = require("when");
var should = require("should"); var should = require("should");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var websocketNode = require("../../../../nodes/core/io/22-websocket.js"); var websocketNode = require("../../../../nodes/core/io/22-websocket.js");
var sockets = []; var sockets = [];

View File

@ -17,7 +17,7 @@
var fs = require("fs-extra"); var fs = require("fs-extra");
var path = require("path"); var path = require("path");
var should = require("should"); var should = require("should");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var watchNode = require("../../../../nodes/core/io/23-watch.js"); var watchNode = require("../../../../nodes/core/io/23-watch.js");

View File

@ -17,7 +17,8 @@
var net = require("net"); var net = require("net");
var should = require("should"); var should = require("should");
var stoppable = require('stoppable'); var stoppable = require('stoppable');
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var tcpinNode = require("../../../../nodes/core/io/31-tcpin.js"); var tcpinNode = require("../../../../nodes/core/io/31-tcpin.js");
@ -46,7 +47,7 @@ describe('TCP in Node', function() {
sock.end(); sock.end();
} }
} }
function startServer(done) { function startServer(done) {
server_port += 1; server_port += 1;
server = stoppable(net.createServer(function(c) { server = stoppable(net.createServer(function(c) {
@ -59,7 +60,7 @@ describe('TCP in Node', function() {
function stopServer(done) { function stopServer(done) {
server.stop(done); server.stop(done);
} }
function send(wdata) { function send(wdata) {
var opt = {port:port, host:"localhost"}; var opt = {port:port, host:"localhost"};
var client = net.createConnection(opt, function() { var client = net.createConnection(opt, function() {

View File

@ -17,7 +17,7 @@
var net = require("net"); var net = require("net");
var should = require("should"); var should = require("should");
var stoppable = require('stoppable'); var stoppable = require('stoppable');
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var tcpinNode = require("../../../../nodes/core/io/31-tcpin.js"); var tcpinNode = require("../../../../nodes/core/io/31-tcpin.js");
@ -71,7 +71,7 @@ describe('TCP Request Node', function() {
} }
}); });
} }
it('should send & recv data', function(done) { it('should send & recv data', function(done) {
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] }, var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];

View File

@ -16,7 +16,7 @@
var dgram = require("dgram"); var dgram = require("dgram");
var should = require("should"); var should = require("should");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var udpNode = require("../../../../nodes/core/io/32-udp.js"); var udpNode = require("../../../../nodes/core/io/32-udp.js");

View File

@ -16,7 +16,7 @@
var dgram = require("dgram"); var dgram = require("dgram");
var should = require("should"); var should = require("should");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var udpNode = require("../../../../nodes/core/io/32-udp.js"); var udpNode = require("../../../../nodes/core/io/32-udp.js");

View File

@ -17,7 +17,7 @@
var should = require("should"); var should = require("should");
var switchNode = require("../../../../nodes/core/logic/10-switch.js"); var switchNode = require("../../../../nodes/core/logic/10-switch.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var RED = require("../../../../red/red.js"); var RED = require("../../../../red/red.js");
describe('switch Node', function() { describe('switch Node', function() {
@ -103,7 +103,7 @@ describe('switch Node', function() {
helperNode1.on("input", function(msg) { helperNode1.on("input", function(msg) {
try { try {
if (shouldReceive === true) { if (shouldReceive === true) {
msg.payload.should.equal(sendPayload); should.equal(msg.payload,sendPayload);
done(); done();
} else { } else {
should.fail(null, null, "We should never get an input!"); should.fail(null, null, "We should never get an input!");
@ -168,8 +168,6 @@ describe('switch Node', function() {
}); });
} }
it('should check if payload equals given value', function(done) { it('should check if payload equals given value', function(done) {
genericSwitchTest("eq", "Hello", true, true, "Hello", done); genericSwitchTest("eq", "Hello", true, true, "Hello", done);
}); });
@ -258,6 +256,43 @@ describe('switch Node', function() {
genericSwitchTest("regex", "[abc]+", true, true, "abbabac", done); genericSwitchTest("regex", "[abc]+", true, true, "abbabac", done);
}); });
it('should check if payload if of type string ', function(done) {
genericSwitchTest("istype", "string", true, true, "Hello", done);
});
it('should check if payload if of type number ', function(done) {
genericSwitchTest("istype", "number", true, true, 999, done);
});
it('should check if payload if of type number 0', function(done) {
genericSwitchTest("istype", "number", true, true, 0, done);
});
it('should check if payload if of type boolean true', function(done) {
genericSwitchTest("istype", "boolean", true, true, true, done);
});
it('should check if payload if of type boolean false', function(done) {
genericSwitchTest("istype", "boolean", true, true, true, done);
});
it('should check if payload if of type array ', function(done) {
genericSwitchTest("istype", "array", true, true, [1,2,3,"a","b"], done);
});
it('should check if payload if of type buffer ', function(done) {
genericSwitchTest("istype", "buffer", true, true, Buffer.from("Hello"), done);
});
it('should check if payload if of type object ', function(done) {
genericSwitchTest("istype", "object", true, true, {a:1,b:"b",c:true}, done);
});
it('should check if payload if of type JSON string ', function(done) {
genericSwitchTest("istype", "json", true, true, JSON.stringify({a:1,b:"b",c:true}), done);
});
it('should check if payload if of type JSON string (and fail if not) ', function(done) {
genericSwitchTest("istype", "json", true, false, "Hello", done);
});
it('should check if payload if of type null', function(done) {
genericSwitchTest("istype", "null", true, true, null, done);
});
it('should check if payload if of type undefined', function(done) {
genericSwitchTest("istype", "undefined", true, true, undefined, done);
});
it('should match regex with ignore-case flag set true', function(done) { it('should match regex with ignore-case flag set true', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"regex","v":"onetwothree","case":true}],checkall:true,outputs:1,wires:[["helperNode1"]]}, var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"regex","v":"onetwothree","case":true}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}]; {id:"helperNode1", type:"helper", wires:[]}];
@ -433,7 +468,6 @@ describe('switch Node', function() {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"nnull"}],checkall:false,outputs:1,wires:[["helperNode1"]]}, var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"nnull"}],checkall:false,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}]; {id:"helperNode1", type:"helper", wires:[]}];
helper.load(switchNode, flow, function() { helper.load(switchNode, flow, function() {
var switchNode1 = helper.getNode("switchNode1"); var switchNode1 = helper.getNode("switchNode1");
var helperNode1 = helper.getNode("helperNode1"); var helperNode1 = helper.getNode("helperNode1");
@ -787,5 +821,4 @@ describe('switch Node', function() {
n1.receive({payload:1, parts:{index:0, count:4, id:222}}); n1.receive({payload:1, parts:{index:0, count:4, id:222}});
}); });
}); });
}); });

View File

@ -17,7 +17,7 @@
var should = require("should"); var should = require("should");
var changeNode = require("../../../../nodes/core/logic/15-change.js"); var changeNode = require("../../../../nodes/core/logic/15-change.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('change Node', function() { describe('change Node', function() {

View File

@ -17,7 +17,7 @@
var should = require("should"); var should = require("should");
var rangeNode = require("../../../../nodes/core/logic/16-range.js"); var rangeNode = require("../../../../nodes/core/logic/16-range.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('range Node', function() { describe('range Node', function() {

View File

@ -17,7 +17,7 @@
var should = require("should"); var should = require("should");
var splitNode = require("../../../../nodes/core/logic/17-split.js"); var splitNode = require("../../../../nodes/core/logic/17-split.js");
var joinNode = require("../../../../nodes/core/logic/17-split.js"); var joinNode = require("../../../../nodes/core/logic/17-split.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var RED = require("../../../../red/red.js"); var RED = require("../../../../red/red.js");
describe('SPLIT node', function() { describe('SPLIT node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var sortNode = require("../../../../nodes/core/logic/18-sort.js"); var sortNode = require("../../../../nodes/core/logic/18-sort.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var RED = require("../../../../red/red.js"); var RED = require("../../../../red/red.js");
describe('SORT node', function() { describe('SORT node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var batchNode = require("../../../../nodes/core/logic/19-batch.js"); var batchNode = require("../../../../nodes/core/logic/19-batch.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
var RED = require("../../../../red/red.js"); var RED = require("../../../../red/red.js");
describe('BATCH node', function() { describe('BATCH node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var csvNode = require("../../../../nodes/core/parsers/70-CSV.js"); var csvNode = require("../../../../nodes/core/parsers/70-CSV.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('CSV node', function() { describe('CSV node', function() {

View File

@ -19,7 +19,7 @@ var path = require("path");
var fs = require('fs-extra'); var fs = require('fs-extra');
var htmlNode = require("../../../../nodes/core/parsers/70-HTML.js"); var htmlNode = require("../../../../nodes/core/parsers/70-HTML.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('html node', function() { describe('html node', function() {
@ -69,7 +69,7 @@ describe('html node', function() {
}); });
}); });
it('should retrieve header contents if asked to by msg.select - alternative property', function(done) { it('should retrieve header contents if asked to by msg.select - alternative in property', function(done) {
fs.readFile(file, 'utf8', function(err, data) { fs.readFile(file, 'utf8', function(err, data) {
var flow = [{id:"n1",type:"html",property:"foo",wires:[["n2"]],func:"return msg;"}, var flow = [{id:"n1",type:"html",property:"foo",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
@ -79,7 +79,7 @@ describe('html node', function() {
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar'); msg.should.have.property('topic', 'bar');
should.equal(msg.foo, 'This is a test page for node 70-HTML'); msg.foo[0].should.equal('This is a test page for node 70-HTML');
done(); done();
}); });
n1.receive({foo:data,topic:"bar",select:"h1"}); n1.receive({foo:data,topic:"bar",select:"h1"});
@ -87,6 +87,24 @@ describe('html node', function() {
}); });
}); });
it('should retrieve header contents if asked to by msg.select - alternative in and out properties', function(done) {
fs.readFile(file, 'utf8', function(err, data) {
var flow = [{id:"n1",type:"html",property:"foo",outproperty:"bar",tag:"h1",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
helper.load(htmlNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.bar[0].should.equal('This is a test page for node 70-HTML');
done();
});
n1.receive({foo:data,topic:"bar"});
});
});
});
it('should emit an empty array if no matching elements', function(done) { it('should emit an empty array if no matching elements', function(done) {
fs.readFile(file, 'utf8', function(err, data) { fs.readFile(file, 'utf8', function(err, data) {
var flow = [{id:"n1",type:"html",wires:[["n2"]],func:"return msg;"}, var flow = [{id:"n1",type:"html",wires:[["n2"]],func:"return msg;"},

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var jsonNode = require("../../../../nodes/core/parsers/70-JSON.js"); var jsonNode = require("../../../../nodes/core/parsers/70-JSON.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('JSON node', function() { describe('JSON node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var xmlNode = require("../../../../nodes/core/parsers/70-XML.js"); var xmlNode = require("../../../../nodes/core/parsers/70-XML.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('XML node', function() { describe('XML node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should"); var should = require("should");
var yamlNode = require("../../../../nodes/core/parsers/70-YAML.js"); var yamlNode = require("../../../../nodes/core/parsers/70-YAML.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('YAML node', function() { describe('YAML node', function() {

View File

@ -20,7 +20,7 @@ var os = require('os');
var fs = require('fs-extra'); var fs = require('fs-extra');
var sinon = require('sinon'); var sinon = require('sinon');
var tailNode = require("../../../../nodes/core/storage/28-tail.js"); var tailNode = require("../../../../nodes/core/storage/28-tail.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('tail Node', function() { describe('tail Node', function() {

View File

@ -20,7 +20,7 @@ var fs = require('fs-extra');
var os = require('os'); var os = require('os');
var sinon = require("sinon"); var sinon = require("sinon");
var fileNode = require("../../../../nodes/core/storage/50-file.js"); var fileNode = require("../../../../nodes/core/storage/50-file.js");
var helper = require("../../helper.js"); var helper = require("node-red-node-test-helper");
describe('file Nodes', function() { describe('file Nodes', function() {

View File

@ -1,169 +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 sinon = require("sinon");
var when = require("when");
var request = require('supertest');
var express = require("express");
var stoppable = require('stoppable');
var nock;
if (!process.version.match(/^v0\.[0-9]\./)) {
// only set nock for node >= 0.10
try {
nock = require('nock');
} catch (err) {
// nevermind, will skip nock tests
nock = null;
}
}
var RED = require("../../red/red.js");
var redNodes = require("../../red/runtime/nodes");
var flows = require("../../red/runtime/nodes/flows");
var credentials = require("../../red/runtime/nodes/credentials");
var comms = require("../../red/api/editor/comms.js");
var log = require("../../red/util/log.js");
var context = require("../../red/runtime/nodes/context.js");
var events = require("../../red/runtime/events.js");
var http = require('http');
var express = require('express');
var app = express();
var address = '127.0.0.1';
var listenPort = 0; // use ephemeral port
var port;
var url;
var logSpy;
var server;
function helperNode(n) {
RED.nodes.createNode(this, n);
}
module.exports = {
load: function(testNode, testFlows, testCredentials, cb) {
var i;
logSpy = sinon.spy(log,"log");
logSpy.FATAL = log.FATAL;
logSpy.ERROR = log.ERROR;
logSpy.WARN = log.WARN;
logSpy.INFO = log.INFO;
logSpy.DEBUG = log.DEBUG;
logSpy.TRACE = log.TRACE;
logSpy.METRIC = log.METRIC;
if (typeof testCredentials === 'function') {
cb = testCredentials;
testCredentials = {};
}
var storage = {
getFlows: function() {
return when.resolve({flows:testFlows,credentials:testCredentials});
}
};
var settings = {
available: function() { return false; }
};
var red = {};
for (i in RED) {
if (RED.hasOwnProperty(i) && !/^(init|start|stop)$/.test(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(RED,i);
Object.defineProperty(red,i,propDescriptor);
}
}
red["_"] = function(messageId) {
return messageId;
};
redNodes.init({events:events,settings:settings, storage:storage,log:log,});
RED.nodes.registerType("helper", helperNode);
if (Array.isArray(testNode)) {
for (i = 0; i < testNode.length; i++) {
testNode[i](red);
}
} else {
testNode(red);
}
flows.load().then(function() {
flows.startFlows();
should.deepEqual(testFlows, flows.getFlows().flows);
cb();
});
},
unload: function() {
// TODO: any other state to remove between tests?
redNodes.clearRegistry();
logSpy.restore();
context.clean({allNodes:[]});
return flows.stopFlows();
},
getNode: function(id) {
return flows.get(id);
},
credentials: credentials,
clearFlows: function() {
return flows.stopFlows();
},
request: function() {
return request(RED.httpAdmin);
},
startServer: function(done) {
server = stoppable(http.createServer(function(req,res) { app(req,res); }), 0);
RED.init(server, {
SKIP_BUILD_CHECK: true,
logging:{console:{level:'off'}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
url = 'http://' + address + ':' + port;
comms.start();
done();
});
},
//TODO consider saving TCP handshake/server reinit on start/stop/start sequences
stopServer: function(done) {
if (server) {
try {
comms.stop();
server.stop(done);
} catch(e) {
done();
}
} else {
done();
}
},
url: function() { return url; },
nock: nock,
log: function() { return logSpy;}
};

View File

@ -45,6 +45,28 @@ describe("runtime-api/settings", function() {
/* /*
before(function() {
sinon.stub(theme,"settings",function() { return { test: 456 };});
app = express();
app.get("/settings",info.runtimeSettings);
app.get("/settingsWithUser",function(req,res,next) {
req.user = {
username: "nick",
permissions: "*",
image: "http://example.com",
anonymous: false,
private: "secret"
}
next();
},info.runtimeSettings);
});
after(function() {
theme.settings.restore();
});
it('returns the filtered settings', function(done) { it('returns the filtered settings', function(done) {
info.init({ info.init({
settings: { settings: {
@ -77,6 +99,42 @@ describe("runtime-api/settings", function() {
res.body.should.have.property("testNodeSetting","helloWorld"); res.body.should.have.property("testNodeSetting","helloWorld");
res.body.should.not.have.property("foo",123); res.body.should.not.have.property("foo",123);
res.body.should.have.property("flowEncryptionType","test-key-type"); res.body.should.have.property("flowEncryptionType","test-key-type");
res.body.should.not.have.property("user");
done();
});
});
it('returns the filtered user in settings', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {}
});
request(app)
.get("/settingsWithUser")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("user");
res.body.user.should.have.property("username","nick");
res.body.user.should.have.property("permissions","*");
res.body.user.should.have.property("image","http://example.com");
res.body.user.should.have.property("anonymous",false);
res.body.user.should.not.have.property("private");
done(); done();
}); });
}); });

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMDCCAhgCCQDPGPyu5M6ZaDANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTgwMzE2MDY0ODU1WhgP
MjExODAyMjAwNjQ4NTVaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0
YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMM
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMifGelM
k/b3HeIj98y9P5jS+Qblqpq7+gsCaL+qglMFmG0QXe6Ordkrh3xeY0uTkaFatwLM
WMzoX60nNdaVjC9U9RlQLK/3nncCveexxRUGtI8VpxN04ivBE/ULhtJeStQFrfyt
LWr1WWf8o8P/EWzZnh0Y1oHc0XqhOPHu9Nfd9kn5nfHNd/xbY8KXa4DkVSJ1lLFK
3t/nSWttchF8zKgNpoQznNGqUTjT28l0sS8fyH76DyRj3Ke6xdNxX2NRUU0PnGFI
RMsBG4Qrzo5xY7lQP7uVVgZUlxryw+NuZuC1PBXaUKJOf6CGwrTq5WB9zF1iBZCs
wD68NvtLd0kHEgECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfqNOg2v90r5x4lFo
SYmPUoX24gdwHd/mfCzDJksB8n98X1eULYZqqRF2Q7LMkYu/twxfR3EKQX1HZxQY
LpGUYX4ubJdVTy13opJs8B4NkhvRuOAP0+b7RVt4RfuxLX9tYOB98tEbf7Mj0ccq
F4sHi+PMCh64K7rNWECHar0F51yNtNXcxJPMuHZVmj0/U7h6ZxNf+GzdTi8YKmVy
5OHI7xol/II/v3QOi1L+BaEIUkqYODKuQouJVIzu4zX6JRfAaxwjJmliYoJm7OEY
dFMEQUw1Ggsos+KbkGi9mCDbveYpWcZTR8nfPwmx+oJtt47DTHUC3KSdRxgtfjGs
otmVSw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyJ8Z6UyT9vcd4iP3zL0/mNL5BuWqmrv6CwJov6qCUwWYbRBd
7o6t2SuHfF5jS5ORoVq3AsxYzOhfrSc11pWML1T1GVAsr/eedwK957HFFQa0jxWn
E3TiK8ET9QuG0l5K1AWt/K0tavVZZ/yjw/8RbNmeHRjWgdzReqE48e701932Sfmd
8c13/FtjwpdrgORVInWUsUre3+dJa21yEXzMqA2mhDOc0apRONPbyXSxLx/IfvoP
JGPcp7rF03FfY1FRTQ+cYUhEywEbhCvOjnFjuVA/u5VWBlSXGvLD425m4LU8FdpQ
ok5/oIbCtOrlYH3MXWIFkKzAPrw2+0t3SQcSAQIDAQABAoIBAGmbryUrpZxU24tG
idRiLw9Ax8yEq7lGiMqw2vlCRdZ0VJfdDMVeoE945ZpniXeoV/oLadl0Pq6nCG56
/JFYKfJkk51eoheDjwxxCgzkfK2j2PqVWF0ao1CLE/ljtvYYouVXlA42D3mFbCoc
SQ0MwVx+dgg1If48gp0+L17T/ll/VOOQumts5UzoKC8YABLL00g5ZL9/jZlVipgl
HfENMPWOfy3q5kSgQqvTWTMdSE6644ryV890mrwcC/RzqQBSNgRh1Lqx3jcXQSdN
x5C19gEK60hZqcvzBkKYudMHUC6I0lcuao1xwBnHUQIVKmLFPZBUIQq3tVar/YUc
d65cJpECgYEA5D9QilQpHxv875wBvBOEbyt9TqDBBN/5JpGQ9sBKpA0eNp3UzrOr
+n0TlyoDZYjkxgNJScS4TpeKde1Hk5j2kkMngjS69dn4G6wmOI79gAOGrCiJd1/I
AWb09KxUKlWBbfKuLHdl1wSMCYQornDdXxYCxhv9sMZKbEJ//tsI420CgYEA4QPf
n/dRAm+6zwNQTWOYWlj5jsG1TilBBCtoRqUqVlrAgR6rS1lgOleHkVrWH0g0Lkmh
9DxWiWuNNXxdU/5zx9AQn/JuHuL8EjDLN5r7idcg2LtEElCkr12y0I9nzS2OOZnj
MTioIh+hghzNuk09NlVJrHi48bJUVL/6Ws7ruGUCgYBT9UJAD+MscVQiI2Wz9A30
ArBOOu2lSGnSmRsU2PjbzYN+naIJAqhRNK7/HNIxCCD3AYB05SrSpgWliUmZ7ltM
w+0FhTX8d1g/fZx1k4uGCkYAj8y5H39nnKKgWb9/7wH0Gp+c9bJ9XEvSuE1qlVOo
xWTx0JwJ6Xa4yeFhMtrbJQKBgF/FfErjwvEciRBPQsCNoWzi7eUbAYYw/OE/cHSR
HAIBQmoymYnKkrCCTMtLNFPAMaV55ZrEi7iVtFaNhlOXu8PSBSFu1/wBdHRxnC0g
o+s5S1uz6Pc6p72UTeWDBBVKTHyryQ1MJhPQDrgIdm/TLDiR+HeWMnF9C3O++lno
NGAZAoGBAKhsmatxVD9B3jvUDd/CWhXVDSZQECrfJ+Uy1q6b5NO5yMibpIZv14Nj
VT+b2qXoO1wL6htTRJXXNPmrB/JtrLiLg/vxVuA7CPSgot8SDA+/lbRhf1n/SKnD
ECXrEUmq28SgBItbY4vcy5PVEHRvlzqO/LpD6Y7iGNpR7zw9Yk3b
-----END RSA PRIVATE KEY-----