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
Projects

View File

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

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
$(function() {
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = document.title+" : "+window.location.hostname;
}
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") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [

View File

@ -208,6 +208,8 @@ RED.palette.editor = (function() {
if (nodeEntry) {
var activeTypeCount = 0;
var typeCount = 0;
var errorCount = 0;
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
@ -216,7 +218,10 @@ RED.palette.editor = (function() {
var inUseCount = 0;
var set = moduleInfo.sets[setName];
var setElements = nodeEntry.sets[setName];
if (set.err) {
errorCount++;
$("<li>").text(set.err).appendTo(nodeEntry.errorList);
}
if (set.enabled) {
activeTypeCount += set.types.length;
}
@ -255,6 +260,13 @@ RED.palette.editor = (function() {
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;
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);
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 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 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);
@ -620,6 +635,8 @@ RED.palette.editor = (function() {
updateButton: updateButton,
removeButton: removeButton,
enableButton: enableButton,
errorRow: errorRow,
errorList: errorList,
setCount: setCount,
container: container,
shade: shade,
@ -651,7 +668,6 @@ RED.palette.editor = (function() {
typeSwatches[t] = $('<span>',{class:"palette-module-type-swatch"}).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);
enableButton.click(function(evt) {
evt.preventDefault();

View File

@ -1255,6 +1255,14 @@ RED.projects.settings = (function() {
text: 'Delete remote',
click: function() {
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 options = {
url: url,
@ -1276,6 +1284,7 @@ RED.projects.settings = (function() {
activeProject.git.remotes[name] = remote;
});
}
delete activeProject.git.branches.remoteAlt;
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("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;
},
buttons: [
@ -90,13 +110,6 @@ RED.projects = (function() {
createProjectOptions = {};
$( 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.email = gitEmailInput.val();
RED.settings.set('git', currentGitSettings);
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() {
var projectFlowFileInput;
var projectCredentialFileInput;
@ -1127,6 +1504,7 @@ RED.projects = (function() {
}
$(".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");
@ -1873,6 +2251,43 @@ RED.projects = (function() {
createProjectOptions = {};
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) {
$.getJSON("projects",function(data) {
@ -1952,6 +2367,7 @@ RED.projects = (function() {
RED.projects.settings.show('deps');
},
createDefaultFileSet: createDefaultFileSet,
createDefaultPackageFile: createDefaultPackageFile,
// showSidebar: showSidebar,
refresh: refresh,
editProject: function() {

View File

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

View File

@ -49,7 +49,12 @@
.palette-module-version {
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;
}
}
.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 {
@include shade;
text-align: center;

View File

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

View File

@ -204,7 +204,14 @@ module.exports = function(RED) {
}
var context = vm.createContext(sandbox);
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) {
try {
var start = process.hrtime();
@ -219,6 +226,13 @@ module.exports = function(RED) {
this.status({fill:"yellow",shape:"dot",text:""+converted});
}
} 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 errorMessage;

View File

@ -212,48 +212,84 @@
<input type="password" id="node-config-input-password">
</div>
</div>
<div id="mqtt-broker-tab-birth" style="display:none">
<div class="form-row">
<label for="node-config-input-birthTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-config-input-birthTopic" data-i18n="[placeholder]mqtt.placeholder.birth-topic">
<div id="mqtt-broker-tab-messages" style="display:none">
<div id="mqtt-broker-section-birth">
<div class="palette-header">
<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 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:125px !important">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-config-input-birthRetain" style="width:125px !important">
<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 for="node-config-input-birthPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-config-input-birthPayload" data-i18n="[placeholder]common.label.payload">
</div>
</div>
<div id="mqtt-broker-tab-will" style="display:none">
<div class="form-row">
<label for="node-config-input-willTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-config-input-willTopic" data-i18n="[placeholder]mqtt.placeholder.will-topic">
</div>
<div class="form-row">
<label for="node-config-input-willQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-config-input-willQos" style="width:125px !important">
<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>
&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">
</div>
</div>
</div>
<div id="mqtt-broker-section-close">
<div class="palette-header">
<i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.close-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-closeTopic"><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-closeTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.close-topic">
<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>
<select id="node-config-input-closeRetain" 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 for="node-config-input-willPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-config-input-willPayload" data-i18n="[placeholder]common.label.payload">
<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 id="mqtt-broker-section-will">
<div class="palette-header">
<i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.will-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-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">
<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>
<select id="node-config-input-willRetain" 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-willPayload"><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-willPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
<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>
<select id="node-config-input-willQos" 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>
@ -269,6 +305,9 @@
<h4>Birth Message</h4>
<p>This is a message that will be published on the configured topic whenever the
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>
<p>This is a message that will be published by the broker in the event the node
unexpectedly loses its connection.</p>
@ -300,14 +339,18 @@
compatmode: { value: true},
keepalive: {value:60,validate:RED.validators.number()},
cleansession: {value: true},
willTopic: {value:""},
willQos: {value:"0"},
willRetain: {value:false},
willPayload: {value:""},
birthTopic: {value:""},
birthQos: {value:"0"},
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: {
user: {type:"text"},
@ -343,14 +386,39 @@
id: "mqtt-broker-tab-security",
label: this._("mqtt.tabs-label.security")
});
tabs.addTab({
id: "mqtt-broker-tab-birth",
label: this._("mqtt.tabs-label.birth")
id: "mqtt-broker-tab-messages",
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);
if (typeof this.cleansession === 'undefined') {
this.cleansession = true;
@ -368,14 +436,18 @@
this.keepalive = 15;
$("#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') {
this.birthQos = "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() {
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) {
this.username = this.credentials.user;
this.password = this.credentials.password;
@ -314,6 +323,10 @@ module.exports = function(RED) {
this.on('close', function(done) {
this.closing = true;
if (this.connected) {
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage);
}
this.client.once('close', function() {
done();
});

View File

@ -29,7 +29,7 @@
</div>
<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>
<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 class="form-row">
<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) {
"use strict";
var os = require('os');
var dgram = require('dgram');
var udpInputPortsInUse = {};
@ -30,6 +31,29 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4";
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};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var server;
@ -39,7 +63,7 @@ module.exports = function(RED) {
udpInputPortsInUse[this.port] = server;
}
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
}
@ -121,12 +145,38 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4";
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};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var sock;
if (udpInputPortsInUse[this.outport || this.port]) {
sock = udpInputPortsInUse[this.outport || this.port];
var p = 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 {
sock = dgram.createSocket(opts); // default to udp4
@ -136,18 +186,16 @@ module.exports = function(RED) {
// prevent it going to the global error handler and shutting node-red
// down.
});
udpInputPortsInUse[this.outport || this.port] = sock;
}
udpInputPortsInUse[p] = sock;
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.setBroadcast(true); // turn on broadcast
if (node.multicast == "multi") {
try {
sock.setMulticastTTL(128);
sock.addMembership(node.addr,node.iface); // Add to the multicast group
node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port}));
node.log(RED._("udp.status.mc-ready",{iface:node.iface,outport:node.outport,host:node.addr,port:node.port}));
} catch (e) {
if (e.errno == "EINVAL") {
node.error(RED._("udp.errors.bad-mcaddress"));
@ -167,6 +215,7 @@ module.exports = function(RED) {
} else {
node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
}
}
node.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
@ -198,8 +247,8 @@ module.exports = function(RED) {
});
node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.outport || node.port)) {
delete udpInputPortsInUse[node.outport || node.port];
if (udpInputPortsInUse.hasOwnProperty(p)) {
delete udpInputPortsInUse[p];
}
try {
sock.close();

View File

@ -323,6 +323,7 @@
"broker": "Server",
"example": "e.g. localhost",
"qos": "QoS",
"retain": "Retain",
"clientid": "Client ID",
"port": "Port",
"keepalive": "Keep alive time (s)",
@ -332,17 +333,22 @@
"verify-server-cert":"Verify server certificate",
"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": {
"connection": "Connection",
"security": "Security",
"will": "Will Message",
"birth": "Birth Message"
"messages": "Messages"
},
"placeholder": {
"clientid": "Leave blank for auto generated",
"clientid-nonclean":"Must be set for non-clean sessions",
"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": {
"connected": "Connected to broker: __broker__",
@ -495,15 +501,15 @@
"using": "using",
"output": "Output",
"group": "Group",
"interface": "Local IP",
"interfaceprompt": "(optional) local ip address to bind to",
"interface": "Local IF",
"send": "Send a",
"toport": "to port",
"address": "Address",
"decode-base64": "Decode Base64 encoded payload?"
},
"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"
},
"udpmsgs": "udp messages",
@ -531,10 +537,11 @@
"mc-group": "udp multicast group __group__",
"listener-stopped": "udp listener 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__",
"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": {
"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",
"port-notset": "udp: port not set",
"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": {
@ -566,6 +574,7 @@
"false":"is false",
"null":"is null",
"nnull":"is not null",
"istype":"is of type",
"head":"head",
"tail":"tail",
"index":"index between",
@ -671,7 +680,8 @@
"html": {
"label": {
"select": "Selector",
"output": "Output"
"output": "Output",
"in": "in"
},
"output": {
"html": "the html content of the elements",

View File

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

View File

@ -525,7 +525,8 @@
"mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__",
"bc-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": {
"access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口",

View File

@ -85,6 +85,7 @@
{v:"false",t:"switch.rules.false",kind:'V'},
{v:"null",t:"switch.rules.null",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:"index",t:"switch.rules.index",kind:'S'},
{v:"tail",t:"switch.rules.tail",kind:'S'},
@ -160,6 +161,7 @@
var selectField = rule.find("select");
var type = selectField.val()||"";
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 expField = rule.find(".node-input-rule-exp-value");
var btwnField1 = rule.find(".node-input-rule-btwn-value");
@ -180,6 +182,8 @@
numField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "jsonata_exp") {
expField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "istype") {
typeField.typedInput("width",(newWidth-selectWidth-70));
} else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
// 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 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 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);
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);
@ -243,26 +259,39 @@
valueField.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('hide');
typeValueField.typedInput('hide');
btwnValueField.typedInput('show');
} else if ((type === "head") || (type === "tail")) {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('show');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
} else if (type === "jsonata_exp") {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('show');
numValueField.typedInput('hide');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
} else {
btwnValueField.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('hide');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
valueField.typedInput('hide');
} else {
typeValueField.typedInput('hide');
}
else
if (type === "istype") {
valueField.typedInput('hide');
typeValueField.typedInput('show');
}
else {
typeValueField.typedInput('hide');
valueField.typedInput('show');
}
}
@ -287,6 +316,9 @@
} else if ((rule.t === "head") || (rule.t === "tail")) {
numValueField.typedInput('value',rule.v);
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") {
expValueField.typedInput('value',rule.v);
expValueField.typedInput('type',rule.vt||'jsonata');
@ -359,6 +391,9 @@
} else if ((type === "head") || (type === "tail")) {
r.v = rule.find(".node-input-rule-num-value").typedInput('value');
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") {
r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
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; },
'null': 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) {
var count = Number(b);
return (parts.index < count);
@ -292,6 +302,10 @@ module.exports = function(RED) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else if (rule.vt === 'json') {
v1 = "json";
} else if (rule.vt === 'null') {
v1 = "null";
} else {
try {
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">
<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>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%">
</div>
<div class="form-row">
<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>
</select>
</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/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@ -59,6 +63,7 @@
defaults: {
name: {value:""},
property: {value:"payload"},
outproperty: {value:"payload"},
tag: {value:""},
ret: {value:"html"},
as: {value:"single"}
@ -74,6 +79,7 @@
},
oneditprepare: function() {
$("#node-input-property").typedInput({default:'msg',types:['msg']});
$("#node-input-outproperty").typedInput({default:'msg',types:['msg']});
}
});
</script>

View File

@ -21,6 +21,7 @@ module.exports = function(RED) {
function CheerioNode(n) {
RED.nodes.createNode(this,n);
this.property = n.property||"payload";
this.outproperty = n.outproperty||this.property||"payload";
this.tag = n.tag;
this.ret = n.ret || "html";
this.as = n.as || "single";
@ -48,7 +49,7 @@ module.exports = function(RED) {
/* istanbul ignore else */
if (pay2) {
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 = {
id: msg._msgid,
index: index,
@ -68,7 +69,7 @@ module.exports = function(RED) {
index++;
});
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);
}
}

View File

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

2
red.js
View File

@ -276,7 +276,7 @@ function getListenPath() {
}
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;
if (settings.httpAdminRoot !== false) {
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_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_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_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>"

View File

@ -68,10 +68,17 @@ var api = module.exports = {
try {
var safeSettings = {
httpNodeRoot: runtime.settings.httpNodeRoot||"/",
version: runtime.settings.version,
user: opts.user
version: runtime.settings.version
}
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)) {
safeSettings.paletteCategories = runtime.settings.paletteCategories;
}

View File

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

View File

@ -53,15 +53,13 @@ function getGitUser(user) {
}
return null;
}
function Project(name) {
this.name = name;
this.path = fspath.join(projectsDir,name);
function Project(path) {
this.path = path;
this.name = fspath.basename(path);
this.paths = {};
this.files = {};
this.auth = {origin:{}};
this.missingFiles = [];
this.credentialSecret = null;
}
Project.prototype.load = function () {
@ -70,7 +68,9 @@ Project.prototype.load = function () {
// console.log(globalProjectSettings)
var projectSettings = {};
if (globalProjectSettings) {
projectSettings = globalProjectSettings.projects[this.name]||{};
if (globalProjectSettings.projects.hasOwnProperty(this.name)) {
projectSettings = globalProjectSettings.projects[this.name] || {};
}
}
this.credentialSecret = projectSettings.credentialSecret;
@ -81,9 +81,7 @@ Project.prototype.load = function () {
var promises = [];
return checkProjectFiles(project).then(function(missingFiles) {
if (missingFiles.length > 0) {
project.missingFiles = missingFiles;
}
if (missingFiles.indexOf('package.json') === -1) {
project.paths['package.json'] = fspath.join(project.path,"package.json");
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) {
var project = this;
if (!this.empty) {
throw new Error("Cannot initialise non-empty project");
}
// if (!this.empty) {
// throw new Error("Cannot initialise non-empty project");
// }
var files = Object.keys(defaultFileSet);
var promises = [];
@ -148,6 +146,8 @@ Project.prototype.initialise = function(user,data) {
promises.push(settings.set('projects',projects));
}
if (data.hasOwnProperty('files')) {
if (data.files.hasOwnProperty('flow') && data.files.hasOwnProperty('credentials')) {
project.files.flow = data.files.flow;
project.files.credentials = data.files.credentials;
var flowFilePath = fspath.join(project.path,project.files.flow);
@ -156,9 +156,15 @@ Project.prototype.initialise = function(user,data) {
promises.push(util.writeFile(credsFilePath,"{}"));
files.push(project.files.flow);
files.push(project.files.credentials);
}
}
for (var file in defaultFileSet) {
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());
}
Project.prototype.toJSON = function () {
Project.prototype.export = function () {
return {
name: this.name,
@ -780,28 +786,18 @@ function getBackupFilename(filename) {
return fspath.join(ffDir,"."+ffName+".backup");
}
function checkProjectExists(project) {
var projectPath = fspath.join(projectsDir,project);
function checkProjectExists(projectPath) {
return fs.pathExists(projectPath).then(function(exists) {
if (!exists) {
var e = new Error("Project not found: "+project);
var e = new Error("Project not found");
e.code = "project_not_found";
e.project = project;
var name = fspath.basename(projectPath);
e.project = name;
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) {
var projectPath = fspath.join(projectsDir,project.name);
// Create a basic skeleton of a project
@ -904,17 +900,23 @@ function createProject(user, metadata) {
} else {
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 projectPath = metadata.path;
return new Promise(function(resolve,reject) {
var projectPath = fspath.join(projectsDir,project);
fs.stat(projectPath, function(err,stat) {
if (!err) {
var e = new Error("NLS: Project already exists");
e.code = "project_exists";
return reject(e);
}
createProjectDirectory(project).then(function() {
fs.ensureDir(projectPath).then(function() {
var projects = settings.get('projects');
if (!projects) {
projects = {
@ -951,7 +953,7 @@ function createProject(user, metadata) {
return createDefaultProject(user, metadata);
}
}).then(function() {
resolve(getProject(project))
resolve(loadProject(projectPath))
}).catch(function(err) {
fs.remove(projectPath,function() {
reject(err);
@ -961,50 +963,21 @@ function createProject(user, metadata) {
})
}
function deleteProject(user, name) {
return checkProjectExists(name).then(function() {
if (currentProject && currentProject.name === name) {
var e = new Error("NLS: Can't delete the active project");
e.code = "cannot_delete_active_project";
throw e;
}
else {
return deleteProjectDirectory(name).then(function() {
function deleteProject(user, projectPath) {
return checkProjectExists(projectPath).then(function() {
return fs.remove(projectPath).then(function() {
var name = fspath.basename(projectPath);
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 = {
init: init,
get: getProject,
load: loadProject,
create: createProject,
delete: deleteProject,
list: listProjects
delete: deleteProject
}

View File

@ -48,7 +48,9 @@ function runGitCommand(args,cwd,env) {
var err = new Error(stderr);
err.stdout = stdout;
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
err.code = "git_auth_failed";
} 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)) {
// TODO: handle host key verification errors separately
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)) {
err.code = "git_local_overwrite";
} else if (/CONFLICT/.test(err.stdout)) {

View File

@ -127,10 +127,20 @@ function init(_settings, _runtime) {
activeProject = globalSettings.projects.activeProject;
}
if (settings.flowFile) {
// if flowFile is a known project name - use it
if (globalSettings.projects.projects.hasOwnProperty(settings.flowFile)) {
activeProject = settings.flowFile;
globalSettings.projects.activeProject = settings.flowFile;
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) {
@ -148,6 +158,24 @@ function init(_settings, _runtime) {
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) {
var userSettings = settings.getUserSettings(user)||{};
return userSettings.git;
@ -160,7 +188,11 @@ function getBackupFilename(filename) {
}
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;
flowsFullPath = project.getFlowFile();
flowsFileBackup = project.getFlowFileBackup();
@ -170,26 +202,20 @@ function loadProject(name) {
})
}
function listProjects(user) {
return Projects.list();
}
function getProject(user, name) {
checkActiveProject(name);
//return when.resolve(activeProject.info);
var username;
if (!user) {
username = "_";
} else {
username = user.username;
}
return Projects.get(name).then(function(project) {
return project.toJSON();
});
return Promise.resolve(activeProject.export());
}
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) {
@ -347,6 +373,7 @@ function createProject(user, metadata) {
metadata.files.oldCredentials = credentialsFile;
metadata.files.credentialSecret = currentEncryptionKey;
}
metadata.path = fspath.join(projectsDir,metadata.name);
return Projects.create(user, metadata).then(function(p) {
return setActiveProject(user, p.name);
}).then(function() {
@ -479,6 +506,12 @@ function getFlows() {
error.code = "project_empty";
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()) {
log.warn("Project has no flow file");
error = new Error("Project has no flow file");

View File

@ -80,9 +80,8 @@ module.exports = {
*/
writeFile: function(path,content,backupPath) {
if (backupPath) {
try {
if (fs.existsSync(path)) {
fs.renameSync(path,backupPath);
} catch(err) {
}
}
return when.promise(function(resolve,reject) {

View File

@ -23,6 +23,7 @@ module.exports = {
uiPort: process.env.PORT || 1880,
// 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
// example, the following would only allow connections from the local machine.
//uiHost: "127.0.0.1",

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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');
describe('debug node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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 osType = require("os").type();

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {
@ -242,7 +242,7 @@ describe('function node', function() {
});
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() {
var n1 = helper.getNode("n1");
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('id', 'n1');
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();
} catch(err) {
done(err);

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -17,7 +17,7 @@
var should = require("should");
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;

View File

@ -16,7 +16,7 @@
var should = require("should");
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 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) {
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"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
@ -434,7 +434,7 @@ describe('trigger node', function() {
else {
msg.should.have.a.property("payload", "World");
//console.log(Date.now() - ss);
(Date.now() - ss).should.be.greaterThan(70);
(Date.now() - ss).should.be.greaterThan(140);
done();
}
}
@ -447,7 +447,7 @@ describe('trigger node', function() {
},20);
setTimeout( function() {
n1.emit("input", {payload:"World"});
},80);
},150);
});
});

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
var ws = require("ws");
var when = require("when");
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 sockets = [];

View File

@ -17,7 +17,7 @@
var fs = require("fs-extra");
var path = require("path");
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");

View File

@ -17,7 +17,8 @@
var net = require("net");
var should = require("should");
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");

View File

@ -17,7 +17,7 @@
var net = require("net");
var should = require("should");
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");

View File

@ -16,7 +16,7 @@
var dgram = require("dgram");
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");

View File

@ -16,7 +16,7 @@
var dgram = require("dgram");
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");

View File

@ -17,7 +17,7 @@
var should = require("should");
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");
describe('switch Node', function() {
@ -103,7 +103,7 @@ describe('switch Node', function() {
helperNode1.on("input", function(msg) {
try {
if (shouldReceive === true) {
msg.payload.should.equal(sendPayload);
should.equal(msg.payload,sendPayload);
done();
} else {
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) {
genericSwitchTest("eq", "Hello", true, true, "Hello", done);
});
@ -258,6 +256,43 @@ describe('switch Node', function() {
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) {
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:[]}];
@ -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"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(switchNode, flow, function() {
var switchNode1 = helper.getNode("switchNode1");
var helperNode1 = helper.getNode("helperNode1");
@ -787,5 +821,4 @@ describe('switch Node', function() {
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
});
});
});

View File

@ -17,7 +17,7 @@
var should = require("should");
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() {

View File

@ -17,7 +17,7 @@
var should = require("should");
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() {

View File

@ -17,7 +17,7 @@
var should = require("should");
var splitNode = 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");
describe('SPLIT node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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");
describe('SORT node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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");
describe('BATCH node', function() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -19,7 +19,7 @@ var path = require("path");
var fs = require('fs-extra');
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() {
@ -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) {
var flow = [{id:"n1",type:"html",property:"foo",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
@ -79,7 +79,7 @@ describe('html node', function() {
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
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();
});
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) {
fs.readFile(file, 'utf8', function(err, data) {
var flow = [{id:"n1",type:"html",wires:[["n2"]],func:"return msg;"},

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -16,7 +16,7 @@
var should = require("should");
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() {

View File

@ -20,7 +20,7 @@ var os = require('os');
var fs = require('fs-extra');
var sinon = require('sinon');
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() {

View File

@ -20,7 +20,7 @@ var fs = require('fs-extra');
var os = require('os');
var sinon = require("sinon");
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() {

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) {
info.init({
settings: {
@ -77,6 +99,42 @@ describe("runtime-api/settings", function() {
res.body.should.have.property("testNodeSetting","helloWorld");
res.body.should.not.have.property("foo",123);
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();
});
});

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-----