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

Merge branch 'dev' into hide-label

This commit is contained in:
Nick O'Leary 2018-10-23 10:55:27 +01:00 committed by GitHub
commit daf3e6a47a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1976 additions and 561 deletions

View File

@ -156,6 +156,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/library.js",

View File

@ -99,6 +99,7 @@
"istanbul": "0.4.5",
"minami": "1.2.3",
"mocha": "^5.2.0",
"mosca": "^2.8.3",
"should": "^8.4.0",
"sinon": "1.17.7",
"stoppable": "^1.0.6",

View File

@ -39,8 +39,9 @@ server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange))
function init(_settings,storage) {
settings = _settings;
if (settings.adminAuth) {
Users.init(settings.adminAuth);
Tokens.init(settings.adminAuth,storage);
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
Users.init(mergedAdminAuth);
Tokens.init(mergedAdminAuth,storage);
}
}
@ -79,23 +80,24 @@ function getToken(req,res,next) {
function login(req,res) {
var response = {};
if (settings.adminAuth) {
if (settings.adminAuth.type === "credentials") {
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
if (mergedAdminAuth.type === "credentials") {
response = {
"type":"credentials",
"prompts":[{id:"username",type:"text",label:"user.username"},{id:"password",type:"password",label:"user.password"}]
}
} else if (settings.adminAuth.type === "strategy") {
} else if (mergedAdminAuth.type === "strategy") {
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot;
response = {
"type":"strategy",
"prompts":[{type:"button",label:settings.adminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]
"prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]
}
if (settings.adminAuth.strategy.icon) {
response.prompts[0].icon = settings.adminAuth.strategy.icon;
if (mergedAdminAuth.strategy.icon) {
response.prompts[0].icon = mergedAdminAuth.strategy.icon;
}
if (settings.adminAuth.strategy.image) {
response.prompts[0].image = theme.serveFile('/login/',settings.adminAuth.strategy.image);
if (mergedAdminAuth.strategy.image) {
response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
}
}
if (theme.context().login && theme.context().login.image) {

View File

@ -32,6 +32,8 @@ var sessions = {};
var loadedSessions = null;
var apiAccessTokens;
function expireSessions() {
var now = Date.now();
var modified = false;
@ -67,16 +69,33 @@ module.exports = {
// At this point, storage will not have been initialised, so defer loading
// the sessions until there's a request for them.
loadedSessions = null;
apiAccessTokens = {};
if ( Array.isArray(adminAuthSettings.tokens) ) {
apiAccessTokens = adminAuthSettings.tokens.reduce(function(prev, current) {
prev[current.token] = {
user: current.user,
scope: current.scope
};
return prev;
}, {});
}
return Promise.resolve();
},
get: function(token) {
return loadSessions().then(function() {
var info = apiAccessTokens[token] || null;
if (info) {
return Promise.resolve(info);
} else {
if (sessions[token]) {
if (sessions[token].expires < Date.now()) {
return expireSessions().then(function() { return null });
}
}
return Promise.resolve(sessions[token]);
}
});
},
create: function(user,client,scope) {

View File

@ -64,6 +64,11 @@ module.exports = {
}
});
}
if (settings.httpServerOptions) {
for (var eOption in settings.httpServerOptions) {
editorApp.set(eOption, settings.httpServerOptions[eOption]);
}
}
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,apiUtil.errorHandler);

View File

@ -21,6 +21,18 @@ var i18n = require("@node-red/util").i18n; // TODO: separate module
var runtimeAPI;
function loadResource(lang, namespace) {
var catalog = i18n.i.getResourceBundle(lang, namespace);
if (!catalog) {
var parts = lang.split("-");
if (parts.length == 2) {
var new_lang = parts[0];
return i18n.i.getResourceBundle(new_lang, namespace);
}
}
return catalog;
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
@ -33,7 +45,7 @@ module.exports = {
var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){
var catalog = i18n.i.getResourceBundle(lang, namespace);
var catalog = loadResource(lang, namespace);
res.json(catalog||{});
});
i18n.i.changeLanguage(prevLang);

View File

@ -20,6 +20,58 @@ var apiUtils = require("../util");
var runtimeAPI;
var needsPermission = require("../auth").needsPermission;
function listProjects(req,res) {
var opts = {
user: req.user
}
runtimeAPI.projects.listProjects(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
}
function getProject(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getProject(opts).then(function(data) {
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
function getProjectStatus(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.query.remote
}
runtimeAPI.projects.getStatus(opts).then(function(data){
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
function getProjectRemotes(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getRemotes(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
@ -40,16 +92,7 @@ module.exports = {
// Projects
// List all projects
app.get("/", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.projects.listProjects(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
app.get("/", needsPermission("projects.read"),listProjects);
// Create project
app.post("/", needsPermission("projects.write"), function(req,res) {
@ -74,13 +117,13 @@ module.exports = {
if (req.body.active) {
runtimeAPI.projects.setActiveProject(opts).then(function() {
res.redirect(303,req.baseUrl + '/');
listProjects(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else if (req.body.initialise) {
runtimeAPI.projects.initialiseProject(opts).then(function() {
res.redirect(303,req.baseUrl + '/'+ req.params.id);
getProject(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -91,7 +134,7 @@ module.exports = {
req.body.hasOwnProperty('files') ||
req.body.hasOwnProperty('git')) {
runtimeAPI.projects.updateProject(opts).then(function() {
res.redirect(303,req.baseUrl + '/'+ req.params.id);
getProject(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -101,21 +144,7 @@ module.exports = {
});
// Get project metadata
app.get("/:id", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getProject(opts).then(function(data) {
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
app.get("/:id", needsPermission("projects.read"), getProject);
// Delete project
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
@ -132,22 +161,7 @@ module.exports = {
// Get project status - files, commit counts, branch info
app.get("/:id/status", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.query.remote
}
runtimeAPI.projects.getStatus(opts).then(function(data){
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
app.get("/:id/status", needsPermission("projects.read"), getProjectStatus);
// Project file listing
@ -203,7 +217,7 @@ module.exports = {
path: req.params[0]
}
runtimeAPI.projects.stageFile(opts).then(function() {
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -217,7 +231,7 @@ module.exports = {
path: req.body.files
}
runtimeAPI.projects.stageFile(opts).then(function() {
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -231,7 +245,7 @@ module.exports = {
message: req.body.message
}
runtimeAPI.projects.commit(opts).then(function() {
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -245,7 +259,7 @@ module.exports = {
path: req.params[0]
}
runtimeAPI.projects.unstageFile(opts).then(function() {
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -258,7 +272,7 @@ module.exports = {
id: req.params.id
}
runtimeAPI.projects.unstageFile(opts).then(function() {
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -442,17 +456,7 @@ module.exports = {
});
// Get a list of remotes
app.get("/:id/remotes", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getRemotes(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
app.get("/:id/remotes", needsPermission("projects.read"), getProjectRemotes);
// Add a remote
app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) {
@ -466,7 +470,7 @@ module.exports = {
return;
}
runtimeAPI.projects.addRemote(opts).then(function(data) {
res.redirect(303,req.baseUrl+"/"+opts.id+"/remotes");
getProjectRemotes(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
@ -480,7 +484,7 @@ module.exports = {
remote: req.params.remoteName
}
runtimeAPI.projects.removeRemote(opts).then(function(data) {
res.redirect(303,req.baseUrl+"/"+opts.id+"/remotes");
getProjectRemotes(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})

View File

@ -23,6 +23,7 @@
"confirmDelete": "Confirm delete",
"delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here",
"addFlow": "Add Flow",
"status": "Status",
"enabled": "Enabled",
"disabled":"Disabled",
@ -46,6 +47,9 @@
"sidebar": {
"show": "Show sidebar"
},
"palette": {
"show": "Show palette"
},
"settings": "Settings",
"userSettings": "User Settings",
"nodes": "Nodes",
@ -212,6 +216,10 @@
"plusNMore": "+ __count__ more"
}
},
"eventLog": {
"title": "Event log",
"view": "View log"
},
"diff": {
"unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts",
@ -444,6 +452,7 @@
"label": "info",
"node": "Node",
"type": "Type",
"module": "Module",
"id": "ID",
"status": "Status",
"enabled": "Enabled",

View File

@ -78,6 +78,12 @@
"projects-settings": "設定"
}
},
"actions": {
"toggle-navigator": "ナビゲータの表示/非表示を切替",
"zoom-out": "縮小",
"zoom-reset": "拡大/縮小を初期化",
"zoom-in": "拡大"
},
"user": {
"loggedInAs": "__name__ としてログインしました",
"username": "ユーザ名",
@ -334,6 +340,10 @@
"analysis": "分析",
"advanced": "その他"
},
"actions": {
"collapse-all": "全カテゴリを折畳む",
"expand-all": "全カテゴリを展開"
},
"event": {
"nodeAdded": "ノードをパレットへ追加しました:",
"nodeAdded_plural": "ノードをパレットへ追加しました",
@ -486,7 +496,9 @@
"editDescription": "プロジェクトの詳細を編集",
"editDependencies": "プロジェクトの依存関係を編集",
"editReadme": "README.mdを編集",
"showProjectSettings": "プロジェクト設定を表示",
"projectSettings": {
"title": "プロジェクト設定",
"edit": "編集",
"none": "なし",
"install": "インストール",
@ -726,7 +738,7 @@
"repo-not-found": "リポジトリが見つかりません"
},
"default-files": {
"create": "プロジェクト関連ファイルの作成",
"create": "プロジェクト関連ファイルの作成",
"desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。",
"desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。",
"desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。",
@ -742,7 +754,7 @@
"desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。",
"desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。",
"desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。",
"desc7": "キーはプロジェクトファイルと別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。",
"desc7": "キーはプロジェクトファイルと別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。",
"credentials": "認証情報",
"enable": "暗号化を有効にする",
"disable": "暗号化を無効にする",

View File

@ -14,11 +14,13 @@
"ctrl-e": "core:show-export-dialog",
"ctrl-i": "core:show-import-dialog",
"ctrl-space": "core:toggle-sidebar",
"ctrl-p": "core:toggle-palette",
"ctrl-,": "core:show-user-settings",
"ctrl-alt-r": "core:show-remote-diff",
"ctrl-alt-n": "core:new-project",
"ctrl-alt-o": "core:open-project",
"ctrl-g v": "core:show-version-control-tab"
"ctrl-g v": "core:show-version-control-tab",
"ctrl-shift-l": "core:show-event-log"
},
"workspace": {
"backspace": "core:delete-selection",

View File

@ -398,6 +398,10 @@ var RED = (function() {
// Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary();
});
RED.comms.subscribe("event-log/#", function(topic,payload) {
var id = topic.substring(9);
RED.eventLog.log(id,payload);
});
}
function showAbout() {
@ -434,7 +438,9 @@ var RED = (function() {
// {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}}
// ]},
// null,
{id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true},
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
{id:"menu-item-event-log",label:RED._("eventLog.title"),onselect:"core:show-event-log"},
null
]});
menuOptions.push(null);
@ -482,6 +488,7 @@ var RED = (function() {
RED.library.init();
RED.keyboard.init();
RED.palette.init();
RED.eventLog.init();
if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init();
} else {

View File

@ -34,14 +34,24 @@ RED.tabs = (function() {
if (options.vertical) {
wrapper.addClass("red-ui-tabs-vertical");
}
if (options.addButton && typeof options.addButton === 'function') {
if (options.addButton) {
wrapper.addClass("red-ui-tabs-add");
var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
addButton.find('a').click(function(evt) {
evt.preventDefault();
if (typeof options.addButton === 'function') {
options.addButton();
} else if (typeof options.addButton === 'string') {
RED.actions.invoke(options.addButton);
}
})
if (typeof options.addButton === 'string') {
var l = options.addButton;
if (options.addButtonCaption) {
l = options.addButtonCaption
}
RED.popover.tooltip(addButton,l,options.addButton);
}
}
var scrollLeft;
var scrollRight;

View File

@ -537,6 +537,10 @@
} else {
this.selectLabel.text(opt.label);
}
if (this.optionMenu) {
this.optionMenu.remove();
this.optionMenu = null;
}
if (opt.options) {
if (this.optionExpandButton) {
this.optionExpandButton.hide();
@ -627,10 +631,6 @@
}
}
} else {
if (this.optionMenu) {
this.optionMenu.remove();
this.optionMenu = null;
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.hide();
}

View File

@ -498,7 +498,7 @@ RED.diff = (function() {
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, false);
RED.utils.createIconElement(icon_url, iconContainer, false, def, node);
return nodeDiv;
}

View File

@ -808,6 +808,7 @@ RED.editor = (function() {
}
setToggleState(node.l);
// If a node has icon property in defaults, the icon of the node cannot be modified. (e.g, ui_button node in dashboard)
if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) {
var iconRow = $('<div class="form-row"></div>').appendTo(dialogForm);
$('<label data-i18n="editor.settingIcon">').appendTo(iconRow);
@ -819,7 +820,7 @@ RED.editor = (function() {
var icon_url = RED.utils.getNodeIcon(node._def,node);
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, true);
RED.utils.createIconElement(icon_url, iconContainer, true, node._def, node);
iconButton.click(function(e) {
e.preventDefault();
@ -833,9 +834,9 @@ RED.editor = (function() {
showIconPicker(iconRow,node,iconPath,function(newIcon) {
$("#node-settings-icon").text(newIcon||"");
var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon});
RED.utils.createIconElement(icon_url, iconContainer, true);
RED.utils.createIconElement(icon_url, iconContainer, true, node._def, node);
});
});
})
$('<div id="node-settings-icon">').text(node.icon).appendTo(iconButton);
}
@ -2220,7 +2221,8 @@ RED.editor = (function() {
} else {
editor.setOptions({
enableBasicAutocompletion:true,
enableSnippets:true
enableSnippets:true,
tooltipFollowsMouse: false
});
}
if (options.readOnly) {

View File

@ -199,6 +199,9 @@ RED.editor.types._markdown = (function() {
}
} else {
editor.session.replace(editor.selection.getRange(), (style.before||"")+current+(style.after||""));
if (current === "") {
editor.gotoLine(range.start.row+1,range.start.column+(style.before||"").length,false);
}
}
editor.focus();
});

View File

@ -0,0 +1,121 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.eventLog = (function() {
var template = '<script type="text/x-red" data-template-name="_eventLog"><div class="form-row node-text-editor-row"><div style="height: 100%;min-height: 150px;" class="node-text-editor" id="event-log-editor"></div></div></script>';
var eventLogEditor;
var backlog = [];
var shown = false;
function appendLogLine(line) {
backlog.push(line);
if (backlog.length > 500) {
backlog = backlog.slice(-500);
}
if (eventLogEditor) {
eventLogEditor.getSession().insert({
row: eventLogEditor.getSession().getLength(),
column: 0
}, "\n" + line);
eventLogEditor.scrollToLine(eventLogEditor.getSession().getLength());
}
}
return {
init: function() {
$(template).appendTo(document.body);
RED.actions.add("core:show-event-log",RED.eventLog.show);
},
show: function() {
if (shown) {
return;
}
shown = true;
var type = "_eventLog"
var trayOptions = {
title: RED._("eventLog.title"),
width: Infinity,
buttons: [
{
id: "node-dialog-close",
text: RED._("common.label.close"),
click: function() {
RED.tray.close();
}
}
],
resize: function(dimensions) {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
eventLogEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
eventLogEditor = RED.editor.createEditor({
id: 'event-log-editor',
value: backlog.join("\n"),
lineNumbers: false,
readOnly: true,
options: {
showPrintMargin: false
}
});
setTimeout(function() {
eventLogEditor.scrollToLine(eventLogEditor.getSession().getLength());
},200);
dialogForm.i18n();
},
close: function() {
eventLogEditor.destroy();
eventLogEditor = null;
shown = false;
},
show: function() {}
}
RED.tray.show(trayOptions);
},
log: function(id,payload) {
var ts = (new Date(payload.ts)).toISOString()+" ";
if (payload.type) {
ts += "["+payload.type+"] "
}
if (payload.data) {
var data = payload.data;
if (data.endsWith('\n')) {
data = data.substring(0,data.length-1);
}
var lines = data.split(/\n/);
lines.forEach(function(line) {
appendLogLine(ts+line);
})
}
},
startEvent: function(name) {
backlog.push("");
backlog.push("-----------------------------------------------------------");
backlog.push((new Date()).toISOString()+" "+name);
backlog.push("");
}
}
})();

View File

@ -863,11 +863,35 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-update",
click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version);
installNodeModule(entry.name,version,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.updateFailed',{module: entry.name,message:xhr.responseJSON.message}));
var notification = RED.notify(RED._('palette.editor.errors.updateFailed',{module: entry.name,message:xhr.responseJSON.message}),{
type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
});
}
}
done(xhr);
@ -898,12 +922,35 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-remove",
click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.remove")+" : "+entry.name);
removeNodeModule(entry.name, function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.removeFailed',{module: entry.name,message:xhr.responseJSON.message}));
var notification = RED.notify(RED._('palette.editor.errors.removeFailed',{module: entry.name,message:xhr.responseJSON.message}),{
type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
}); }
}
})
notification.close();
@ -940,11 +987,36 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-install",
click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}));
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
});
}
}
done(xhr);

View File

@ -20,7 +20,7 @@ RED.palette = (function() {
var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
var categoryContainers = {};
var sidebarControls;
function createCategory(originalCategory,rootCategory,category,ns) {
if ($("#palette-base-category-"+rootCategory).length === 0) {
@ -110,13 +110,26 @@ RED.palette = (function() {
var popOverContent;
try {
var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>";
if (label != type) {
l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b><br/><i>"+type+"</i></p>";
}
popOverContent = $(l+(info?info:$("script[data-help-name='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
popOverContent = $('<div></div>').append($(l+(info?info:$("script[data-help-name='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
.filter(function(n) {
return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2);
}).slice(0,2));
popOverContent.find("a").each(function(){
var linkText = $(this).text();
$(this).before(linkText);
$(this).remove();
});
var typeInfo = RED.nodes.getType(type);
if (typeInfo) {
var metaData = "";
if (typeInfo && !/^subflow:/.test(type)) {
metaData = typeInfo.set.module+" : ";
}
metaData += type;
$('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
}
} catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break
// NON-NLS: internal debug
@ -129,9 +142,9 @@ RED.palette = (function() {
}
function setIcon(element,sf) {
var icon_url = RED.utils.getNodeIcon(sf._def,sf);
var icon_url = RED.utils.getNodeIcon(sf._def);
var iconContainer = element.find(".palette_icon_container");
RED.utils.createIconElement(icon_url, iconContainer, true);
RED.utils.createIconElement(icon_url, iconContainer, true, sf._def);
}
function escapeNodeType(nt) {
@ -169,7 +182,7 @@ RED.palette = (function() {
if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
RED.utils.createIconElement(icon_url, iconContainer, true);
RED.utils.createIconElement(icon_url, iconContainer, true, def);
}
d.style.backgroundColor = RED.utils.getNodeColor(nt,def);
@ -489,6 +502,20 @@ RED.palette = (function() {
}
})
sidebarControls = $('<div class="sidebar-control-left"><i class="fa fa-chevron-left"</div>').appendTo($("#palette"));
sidebarControls.click(function() {
RED.menu.toggleSelected("menu-item-palette");
})
$("#palette").on("mouseenter", function() {
sidebarControls.toggle("slide", { direction: "left" }, 200);
})
$("#palette").on("mouseleave", function() {
sidebarControls.hide();
})
var categoryList = coreCategories;
if (RED.settings.paletteCategories) {
categoryList = RED.settings.paletteCategories;
@ -521,7 +548,28 @@ RED.palette = (function() {
}
});
RED.popover.tooltip($("#palette-expand-all"),RED._('palette.actions.expand-all'));
RED.actions.add("core:toggle-palette", function(state) {
if (state === undefined) {
RED.menu.toggleSelected("menu-item-palette");
} else {
togglePalette(state);
}
});
}
function togglePalette(state) {
if (!state) {
$("#main-container").addClass("palette-closed");
sidebarControls.hide();
sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left");
} else {
$("#main-container").removeClass("palette-closed");
sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left");
}
setTimeout(function() { $(window).resize(); } ,200);
}
function getCategories() {
var categories = [];
$("#palette-container .palette-category").each(function(i,d) {

View File

@ -870,7 +870,13 @@ RED.sidebar.versionControl = (function() {
.click(function(e) {
e.preventDefault();
var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain");
var buttonRow = $('<div style="position: relative; bottom: 60px;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
var activeProject = RED.projects.getActiveProject();
RED.eventLog.startEvent("Push changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
var url = "projects/"+activeProject.name+"/push";
if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt;
@ -914,7 +920,13 @@ RED.sidebar.versionControl = (function() {
var pullRemote = function(options) {
options = options || {};
var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain");
var buttonRow = $('<div style="position: relative; bottom: 60px;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
var activeProject = RED.projects.getActiveProject();
RED.eventLog.startEvent("Pull changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
var url = "projects/"+activeProject.name+"/pull";
if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt;

View File

@ -203,7 +203,7 @@ RED.search = (function() {
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, true);
RED.utils.createIconElement(icon_url, iconContainer, true, node._def, node);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
if (node.z) {

View File

@ -105,6 +105,8 @@ RED.sidebar = (function() {
}
var sidebarSeparator = {};
sidebarSeparator.dragging = false;
$("#sidebar-separator").draggable({
axis: "x",
start:function(event,ui) {
@ -114,6 +116,7 @@ RED.sidebar = (function() {
sidebarSeparator.start = ui.position.left;
sidebarSeparator.chartWidth = $("#workspace").width();
sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2;
sidebarSeparator.dragging = true;
if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true;
@ -166,6 +169,7 @@ RED.sidebar = (function() {
RED.events.emit("sidebar:resize");
},
stop:function(event,ui) {
sidebarSeparator.dragging = false;
if (sidebarSeparator.closing) {
$("#sidebar").removeClass("closing");
RED.menu.setSelected("menu-item-sidebar",false);
@ -181,6 +185,27 @@ RED.sidebar = (function() {
}
});
var sidebarControls = $('<div class="sidebar-control-right"><i class="fa fa-chevron-right"</div>').appendTo($("#sidebar-separator"));
sidebarControls.click(function() {
sidebarControls.hide();
RED.menu.toggleSelected("menu-item-sidebar");
})
$("#sidebar-separator").on("mouseenter", function() {
if (!sidebarSeparator.dragging) {
if (RED.menu.isSelected("menu-item-sidebar")) {
sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left");
} else {
sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left");
}
sidebarControls.toggle("slide", { direction: "right" }, 200);
}
})
$("#sidebar-separator").on("mouseleave", function() {
if (!sidebarSeparator.dragging) {
sidebarControls.hide();
}
})
function toggleSidebar(state) {
if (!state) {
$("#main-container").addClass("sidebar-closed");

View File

@ -133,6 +133,7 @@ RED.sidebar.info = (function() {
var table = $('<table class="node-info"></table>').appendTo(propertiesSection.content);
var tableBody = $('<tbody>').appendTo(table);
var subflowNode;
var subflowUserCount;
@ -207,6 +208,7 @@ RED.sidebar.info = (function() {
$('<span style="float: right; font-size: 0.8em"><i class="fa fa-warning"></i></span>').prependTo($(propRow.children()[1]))
}
}
var count = 0;
if (!m && node.type != "subflow") {
var defaults;
if (node.type === 'unknown') {
@ -218,9 +220,13 @@ RED.sidebar.info = (function() {
})
} else if (node._def) {
defaults = node._def.defaults;
propRow = $('<tr class="node-info-property-row'+(expandedSections.property?"":" hide")+'"><td>'+RED._("sidebar.info.module")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text(RED.nodes.getType(node.type).set.module);
count++;
}
$('<tr class="node-info-property-expand node-info-property-row blank'+(expandedSections.property?"":" hide")+'"><td colspan="2"></td></tr>').appendTo(tableBody);
if (defaults) {
var count = 0;
for (var n in defaults) {
if (n != "name" && n != "info" && defaults.hasOwnProperty(n)) {
var val = node[n];
@ -254,11 +260,11 @@ RED.sidebar.info = (function() {
}
}
}
}
if (count > 0) {
$('<tr class="node-info-property-expand blank"><td colspan="2"><a href="#" class=" node-info-property-header'+(expandedSections.property?" expanded":"")+'"><span class="node-info-property-show-more">'+RED._("sidebar.info.showMore")+'</span><span class="node-info-property-show-less">'+RED._("sidebar.info.showLess")+'</span> <i class="fa fa-caret-down"></i></a></td></tr>').appendTo(tableBody);
}
}
}
if (node.type !== 'tab') {
if (m) {
$('<tr class="blank"><th colspan="2">'+RED._("sidebar.info.subflow")+'</th></tr>').appendTo(tableBody);
@ -308,6 +314,20 @@ RED.sidebar.info = (function() {
$(".node-info-property-row").toggle(expandedSections["property"]);
});
}
// $('<tr class="blank"><th colspan="2"></th></tr>').appendTo(tableBody);
// propRow = $('<tr class="node-info-node-row"><td>Actions</td><td></td></tr>').appendTo(tableBody);
// var actionBar = $(propRow.children()[1]);
//
// // var actionBar = $('<div>',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesSection.content);
// $('<button type="button" class="editor-button"><i class="fa fa-code"></i></button>').appendTo(actionBar);
// $('<button type="button" class="editor-button"><i class="fa fa-code"></i></button>').appendTo(actionBar);
// $('<button type="button" class="editor-button"><i class="fa fa-code"></i></button>').appendTo(actionBar);
// $('<button type="button" class="editor-button"><i class="fa fa-code"></i></button>').appendTo(actionBar);
}
function setInfoText(infoText,target) {
var info = addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(target);

View File

@ -133,7 +133,7 @@ RED.typeSearch = (function() {
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, false);
RED.utils.createIconElement(icon_url, iconContainer, false, def);
if (def.inputs > 0) {
$('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);

View File

@ -767,12 +767,14 @@ RED.utils = (function() {
return RED.settings.apiRootUrl+"icons/node-red/alert.png"
} else if (node && node.icon) {
var iconPath = separateIconPath(node.icon);
if (isIconExists(iconPath)) {
if (iconPath.module === "font-awesome") {
return node.icon;
} else if (isIconExists(iconPath)) {
} else {
return RED.settings.apiRootUrl+"icons/" + node.icon;
}
}
}
var iconPath = getDefaultNodeIcon(def, node);
if (def.category === 'subflows') {
@ -895,10 +897,12 @@ RED.utils = (function() {
/**
* Create or update an icon element and append it to iconContainer.
* @param iconUrl - Url of icon.
* @param iconContainer - icon container element with palette_icon_container class.
* @param isLarge - whether the icon size is large.
* @param iconContainer - Icon container element with palette_icon_container class.
* @param isLarge - Whether the icon size is large.
* @param def - Default definition of a node.
* @param node - If the icon is a specific node instance, this parameter must be specified. If it is icon template such as an icon on the palette, this must be omitted.
*/
function createIconElement(iconUrl, iconContainer, isLarge) {
function createIconElement(iconUrl, iconContainer, isLarge, def, node) {
// Removes the previous icon when icon was changed.
var iconElement = iconContainer.find(".palette_icon");
if (iconElement.length !== 0) {
@ -912,14 +916,24 @@ RED.utils = (function() {
// Show either icon image or font-awesome icon
var iconPath = separateIconPath(iconUrl);
if (iconPath.module === "font-awesome") {
var fontAwesomeUnicode = RED.nodes.fontAwesome.getIconUnicode(iconPath.file);
if (fontAwesomeUnicode) {
var faIconElement = $('<i/>').appendTo(iconContainer);
var faLarge = isLarge ? "fa-lg " : "";
faIconElement.addClass("palette_icon_fa fa fa-fw " + faLarge + iconPath.file);
return;
}
// If the specified name is not defined in font-awesome, show the default icon.
if (def) {
var iconPath = RED.utils.getDefaultNodeIcon(def, node);
iconUrl = RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file;
} else {
iconUrl = RED.settings.apiRootUrl+"icons/node-red/arrow-in.png"
}
}
var imageIconElement = $('<div/>',{class:"palette_icon"}).appendTo(iconContainer);
imageIconElement.css("backgroundImage", "url("+iconUrl+")");
}
}
return {
createObjectElement: buildMessageElement,

View File

@ -1924,7 +1924,6 @@ RED.view = (function() {
.attr("xlink:href",iconUrl)
.attr("class","fa-lg")
.attr("x",15)
.attr("y",21)
.attr("stroke","none")
.attr("fill","#ffffff")
.attr("text-anchor","middle")
@ -2431,6 +2430,7 @@ RED.view = (function() {
thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
thisNode.selectAll(".fa-lg").attr("y",function(d){return (d.h+13)/2;});
thisNode.selectAll(".node_button").attr("opacity",function(d) {
return (activeSubflow||!isButtonEnabled(d))?0.4:1
@ -2764,7 +2764,7 @@ RED.view = (function() {
RED.workspaces.show(new_default_workspace.id);
}
var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
var new_node_ids = new_nodes.map(function(n){ return n.id; });
var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; });
// TODO: pick a more sensible root node
if (new_ms.length > 0) {

View File

@ -150,13 +150,14 @@ RED.workspaces = (function() {
$('<div class="form-row">'+
'<label for="node-input-disabled-btn" data-i18n="editor:workspace.status"></label>'+
'<button id="node-input-disabled-btn" class="editor-button"><i class="fa fa-toggle-on"></i> <span id="node-input-disabled-label"></span></button> '+
'<button type="button" id="node-input-disabled-btn" class="editor-button"><i class="fa fa-toggle-on"></i> <span id="node-input-disabled-label"></span></button> '+
'<input type="checkbox" id="node-input-disabled" style="display: none;"/>'+
'</div>').appendTo(dialogForm);
$('<div class="form-row node-text-editor-row">'+
var row = $('<div class="form-row node-text-editor-row">'+
'<label for="node-input-info" data-i18n="editor:workspace.info" style="width:300px;"></label>'+
'<div style="height:250px;" class="node-text-editor" id="node-input-info"></div>'+
'<div class="node-text-editor-toolbar"></div>'+
'<div style="min-height:250px;" class="node-text-editor" id="node-input-info"></div>'+
'</div>').appendTo(dialogForm);
tabflowEditor = RED.editor.createEditor({
id: 'node-input-info',
@ -164,7 +165,26 @@ RED.workspaces = (function() {
value: ""
});
$('<div class="form-tips" data-i18n="editor:workspace.tip"></div>').appendTo(dialogForm);
var toolbar = RED.editor.types._markdown.buildToolbar(row.find(".node-text-editor-toolbar"),tabflowEditor);
$('<button id="node-info-input-info-expand" class="editor-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(toolbar);
$('#node-info-input-info-expand').click(function(e) {
e.preventDefault();
var value = tabflowEditor.getValue();
RED.editor.editMarkdown({
value: value,
width: "Infinity",
cursor: tabflowEditor.getCursorPosition(),
complete: function(v,cursor) {
tabflowEditor.setValue(v, -1);
tabflowEditor.gotoLine(cursor.row+1,cursor.column,false);
setTimeout(function() {
tabflowEditor.focus();
},300);
}
})
});
dialogForm.find('#node-input-disabled-btn').on("click",function(e) {
var i = $(this).find("i");
@ -269,9 +289,8 @@ RED.workspaces = (function() {
},
minimumActiveTabWidth: 150,
scrollable: true,
addButton: function() {
addWorkspace();
}
addButton: "core:add-flow",
addButtonCaption: RED._("workspace.addFlow")
});
workspaceTabCount = 0;
}
@ -319,11 +338,11 @@ RED.workspaces = (function() {
if (workspace_tabs.contains(ws.id)) {
workspace_tabs.removeTab(ws.id);
}
}
if (ws.id === activeWorkspace) {
activeWorkspace = 0;
}
}
}
function setWorkspaceOrder(order) {
RED.nodes.setWorkspaceOrder(order.filter(function(id) {

View File

@ -6,3 +6,24 @@
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
#event-log-editor {
.ace_scroller {
background: #444;
color: #dd9;
}
.ace_active-line {
background: #666 !important;
}
.ace_selection {
background: #999 !important;
}
}
.ace_tooltip {
background-image: none;
background: #fcffdc;
border-radius: 4px;
@include component-shadow;
border-color: $primary-border-color;
}

View File

@ -28,6 +28,14 @@
transition: width 0.2s ease-in-out;
}
.palette-closed {
#palette { width: 8px; }
#palette-search { display: none; }
#palette-container { display: none; }
#palette-collapse-all { display: none; }
#palette-expand-all { display: none; }
}
.palette-expanded {
& #palette {
width: 380px;

View File

@ -103,3 +103,30 @@
.sidebar-shade {
@include shade;
}
@mixin sidebar-control {
display: none;
position: absolute;
top: calc(50% - 26px);
padding:15px 8px;
border:1px solid #ccc;
background:#f9f9f9;
color: #999;
text-align: center;
cursor: pointer;
}
.sidebar-control-right {
@include sidebar-control;
right: calc(100%);
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.sidebar-control-left {
@include sidebar-control;
left: calc(100%);
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}

View File

@ -40,10 +40,14 @@
right: 322px;
overflow: hidden;
@include component-border;
transition: left 0.2s ease-in-out;
transition: left 0.1s ease-in-out;
}
.palette-closed #workspace {
left: 7px;
}
.workspace-footer-button {
@include component-footer-button;
}

View File

@ -138,14 +138,16 @@ module.exports = function(RED) {
//console.log('[exec] stdout: ' + stdout);
//console.log('[exec] stderr: ' + stderr);
if (error !== null) {
msg3 = {payload:{code:error.code, message:error.message}};
msg3 = RED.util.cloneMessage(msg);
msg3.payload = {code:error.code, message:error.message};
if (error.signal) { msg3.payload.signal = error.signal; }
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
node.log('error:' + error);
}
else if (node.oldrc === "false") {
msg3 = {payload:{code:0}};
msg3 = RED.util.cloneMessage(msg);
msg3.payload = {code:0};
}
if (!msg3) { node.status({}); }
else {

View File

@ -83,7 +83,7 @@
<p>This node can be used to create a timeout within a flow. By default, when
it receives a message, it sends on a message with a <code>payload</code> of <code>1</code>.
It then waits 250ms before sending a second message with a <code>payload</code> of <code>0</code>.
This could be used, for example, to blink an LED attached to a Raspberry PI GPIO pin.</p>
This could be used, for example, to blink an LED attached to a Raspberry Pi GPIO pin.</p>
<p>The payloads of each message sent can be configured to a variety of values, including
the option to not send anything. For example, setting the initial message to <i>nothing</i> and
selecting the option to extend the timer with each received message, the node will

View File

@ -0,0 +1,136 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="http proxy">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-config-input-name">
</div>
<div class="form-row">
<label for="node-config-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-config-input-url" placeholder="http://hostname:port">
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-useAuth" style="width: 70%;"><span data-i18n="httpin.use-proxyauth"></span></label>
<div style="margin-left: 20px" class="node-config-input-useAuth-row hide">
<div class="form-row">
<label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-config-input-username">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-config-input-password">
</div>
</div>
</div>
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span data-i18n="httpin.noproxy-hosts"></span></label>
</div>
<div class="form-row node-config-input-noproxy-container-row">
<ol id="node-config-input-noproxy-container"></ol>
</div>
</script>
<script type="text/x-red" data-help-name="http proxy">
<p>Configuration options for HTTP proxy.</p>
<h3>Details</h3>
<p>When accessing to the host in the ignored host list, no proxy will be used.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('http proxy', {
category: 'config',
defaults: {
name: {value:''},
url: {value:'',validate:function(v) { return (v && (v.indexOf('://') !== -1) && (v.trim().indexOf('http') === 0)); }},
noproxy: {value:[]}
},
credentials: {
username: {type:'text'},
password: {type:'password'}
},
label: function() {
return this.name || this.url || ('http proxy:' + this.id);
},
oneditprepare: function() {
$('#node-config-input-useAuth').change(function() {
if ($(this).is(":checked")) {
$('.node-config-input-useAuth-row').show();
} else {
$('.node-config-input-useAuth-row').hide();
$('#node-config-input-username').val('');
$('#node-config-input-password').val('');
}
});
if (this.credentials.username || this.credentials.has_password) {
$('#node-config-input-useAuth').prop('checked', true);
} else {
$('#node-config-input-useAuth').prop('checked', false);
}
$('#node-config-input-useAuth').change();
var hostList = $('#node-config-input-noproxy-container')
.css({'min-height':'150px','min-width':'450px'})
.editableList({
addItem: function(container, index, data) {
var row = $('<div/>')
.css({overflow: 'hidden',whiteSpace: 'nowrap'})
.appendTo(container);
var hostField = $('<input/>',{class:'node-config-input-host',type:'text',placeholder:'hostname'})
.css({width:'100%'})
.appendTo(row);
if (data.host) {
hostField.val(data.host);
}
},
removable: true
});
if (this.noproxy) {
for (var i in this.noproxy) {
hostList.editableList('addItem', {host:this.noproxy[i]});
}
}
if (hostList.editableList('items').length == 0) {
hostList.editableList('addItem', {host:''});
}
},
oneditsave: function() {
var hosts = $('#node-config-input-noproxy-container').editableList('items');
var node = this;
node.noproxy = [];
hosts.each(function(i) {
var host = $(this).find('.node-config-input-host').val().trim();
if (host) {
node.noproxy.push(host);
}
});
},
oneditresize: function(size) {
var rows = $('#node-config-dialog-edit-form>div:not(.node-config-input-noproxy-container-row)');
var height = size.height;
for (var i = 0; i < rows.size(); i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $('#node-config-dialog-edit-form>div.node-config-input-noproxy-container-row');
height -= (parseInt(editorRow.css('margin-top')) + parseInt(editorRow.css('margin-bottom')));
$('#node-config-input-noproxy-container').editableList('height',height);
}
});
</script>

View File

@ -0,0 +1,33 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
'use strict';
function HTTPProxyConfig(n) {
RED.nodes.createNode(this, n);
this.name = n.name;
this.url = n.url;
this.noproxy = n.noproxy;
};
RED.nodes.registerType('http proxy', HTTPProxyConfig, {
credentials: {
username: {type:'text'},
password: {type:'password'}
}
});
};

View File

@ -53,6 +53,13 @@
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-useProxy" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useProxy" style="width: auto;"><span data-i18n="httpin.use-proxy"></span></label>
<div id="node-input-useProxy-row" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-proxy"><i class="fa fa-globe"></i> <span data-i18n="httpin.proxy-config"></span></label><input type="text" style="width: 270px" id="node-input-proxy">
</div>
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
@ -112,7 +119,7 @@
url to be constructed using values of the incoming message. For example, if the url is set to
<code>example.com/{{{topic}}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.
Using {{{...}}} prevents mustache from escaping characters like / & etc.</p>
<p><b>Note</b>: If running behind a proxy, the standard <code>http_proxy=...</code> environment variable should be set and Node-RED restarted.</p>
<p><b>Note</b>: If running behind a proxy, the standard <code>http_proxy=...</code> environment variable should be set and Node-RED restarted, or use Proxy Configuration. If Proxy Configuration was set, the configuration take precedence over environment variable.</p>
<h4>Using multiple HTTP Request nodes</h4>
<p>In order to use more than one of these nodes in the same flow, care must be taken with
the <code>msg.headers</code> property. The first node will set this property with
@ -141,7 +148,8 @@
method:{value:"GET"},
ret: {value:"txt"},
url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} },
tls: {type:"tls-config",required: false}
tls: {type:"tls-config",required: false},
proxy: {type:"http proxy",required: false}
},
credentials: {
user: {type:"text"},
@ -192,6 +200,24 @@
$("#node-input-usetls").on("click",function() {
updateTLSOptions();
});
function updateProxyOptions() {
if ($("#node-input-useProxy").is(":checked")) {
$("#node-input-useProxy-row").show();
} else {
$("#node-input-useProxy-row").hide();
}
}
if (this.proxy) {
$("#node-input-useProxy").prop("checked", true);
} else {
$("#node-input-useProxy").prop("checked", false);
}
updateProxyOptions();
$("#node-input-useProxy").on("click", function() {
updateProxyOptions();
});
$("#node-input-ret").change(function() {
if ($("#node-input-ret").val() === "obj") {
$("#tip-json").show();
@ -204,6 +230,9 @@
if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_");
}
if (!$("#node-input-useProxy").is(":checked")) {
$("#node-input-proxy").val("_ADD_");
}
}
});
</script>

View File

@ -41,6 +41,13 @@ module.exports = function(RED) {
if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); }
var proxyConfig = null;
if (n.proxy) {
proxyConfig = RED.nodes.getNode(n.proxy);
prox = proxyConfig.url;
noprox = proxyConfig.noproxy;
}
this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
@ -84,6 +91,7 @@ module.exports = function(RED) {
opts.encoding = null; // Force NodeJs to return a Buffer (instead of a string)
opts.maxRedirects = 21;
opts.jar = request.jar();
opts.proxy = null;
var ctSet = "Content-Type"; // set default camel case
var clSet = "Content-Length";
if (msg.headers) {
@ -206,6 +214,15 @@ module.exports = function(RED) {
opts.proxy = null;
}
}
if (proxyConfig && proxyConfig.credentials && opts.proxy == proxyConfig.url) {
var proxyUsername = proxyConfig.credentials.username || '';
var proxyPassword = proxyConfig.credentials.password || '';
if (proxyUsername || proxyPassword) {
opts.headers['proxy-authorization'] =
'Basic ' +
Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64');
}
}
if (tlsNode) {
tlsNode.addTLSOptions(opts);
} else {

View File

@ -28,9 +28,11 @@ module.exports = function(RED) {
this.createDir = n.createDir || false;
var node = this;
node.wstream = null;
node.data = [];
node.msgQueue = [];
node.closing = false;
node.closeCallback = null;
this.on("input",function(msg) {
function processMsg(msg, done) {
var filename = node.filename || msg.filename || "";
if ((!node.filename) && (!node.tout)) {
node.tout = setTimeout(function() {
@ -41,6 +43,7 @@ module.exports = function(RED) {
}
if (filename === "") {
node.warn(RED._("file.errors.nofilename"));
done();
} else if (node.overwriteFile === "delete") {
fs.unlink(filename, function (err) {
if (err) {
@ -51,6 +54,7 @@ module.exports = function(RED) {
}
node.send(msg);
}
done();
});
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename);
@ -59,6 +63,7 @@ module.exports = function(RED) {
fs.ensureDirSync(dir);
} catch(err) {
node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
done();
return;
}
}
@ -70,21 +75,20 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
node.data.push({msg:msg,data:Buffer.from(data)});
while (node.data.length > 0) {
if (node.overwriteFile === "true") {
(function(packet) {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream.on("error", function(err) {
var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream = wstream;
wstream.on("error", function(err) {
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
done();
});
node.wstream.on("open", function() {
node.wstream.end(packet.data, function() {
node.send(packet.msg);
wstream.on("open", function() {
wstream.end(data, function() {
node.send(msg);
done();
});
})
})(node.data.shift());
return;
}
else {
// Append mode
@ -123,32 +127,90 @@ module.exports = function(RED) {
});
node.wstream.on("error", function(err) {
node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg);
done();
});
}
if (node.filename) {
// Static filename - write and reuse the stream next time
var packet = node.data.shift()
node.wstream.write(packet.data, function() {
node.send(packet.msg);
node.wstream.write(data, function() {
node.send(msg);
done();
});
} else {
// Dynamic filename - write and close the stream
var packet = node.data.shift()
node.wstream.end(packet.data, function() {
node.send(packet.msg);
});
node.wstream.end(data, function() {
node.send(msg);
delete node.wstream;
delete node.wstreamIno;
done();
});
}
}
}
else {
done();
}
}
function processQ(queue) {
var msg = queue[0];
processMsg(msg, function() {
queue.shift();
if (queue.length > 0) {
processQ(queue);
}
else if (node.closing) {
closeNode();
}
});
this.on('close', function() {
}
this.on("input", function(msg) {
var msgQueue = node.msgQueue;
if (msgQueue.push(msg) > 1) {
// pending write exists
return;
}
try {
processQ(msgQueue);
}
catch (e) {
node.msgQueue = [];
if (node.closing) {
closeNode();
}
throw e;
}
});
function closeNode() {
if (node.wstream) { node.wstream.end(); }
if (node.tout) { clearTimeout(node.tout); }
node.status({});
var cb = node.closeCallback;
node.closeCallback = null;
node.closing = false;
if (cb) {
cb();
}
}
this.on('close', function(done) {
if (node.closing) {
// already closing
return;
}
node.closing = true;
if (done) {
node.closeCallback = done;
}
if (node.msgQueue.length > 0) {
// close after queue processed
return;
}
else {
closeNode();
}
});
}
RED.nodes.registerType("file",FileNode);

View File

@ -311,7 +311,7 @@
"title": "Title",
"body": "Body"
},
"tip": "Tip: The body text can be styled as <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">Github flavoured Markdown</a>"
"tip": "Tip: The body text can be styled as <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GitHub flavoured Markdown</a>"
},
"unknown": {
"label": {
@ -382,6 +382,10 @@
"basicauth": "Use basic authentication",
"use-tls": "Enable secure (SSL/TLS) connection",
"tls-config":"TLS Configuration",
"use-proxy": "Use proxy",
"proxy-config": "Proxy Configuration",
"use-proxyauth": "Use proxy authentication",
"noproxy-hosts": "Ignore hosts",
"utf8": "a UTF-8 string",
"binary": "a binary buffer",
"json": "a parsed JSON object",

View File

@ -15,7 +15,7 @@
-->
<script type="text/x-red" data-help-name="trigger">
<p>メッセージ受信すると、別のメッセージの送信を行います。延長もしくは初期化が指定されていない場合には、2つ目のメッセージを送信することもできます。</p>
<p>メッセージ受信すると、別のメッセージの送信を行います。延長もしくは初期化が指定されていない場合には、2つ目のメッセージを送信することもできます。</p>
<h3>入力</h3>
<dl class="message-properties">
@ -24,7 +24,7 @@
</dl>
<h3>詳細</h3>
<p>フロー内でタイムアウトを作成するのに利用します。メッセージを受け取ると、デフォルトでは<code>payload</code><code>1</code>を設定して送信します。送信後250ms待機し、<code>payload</code><code>0</code>に設定した2つ目のメッセージを送信します。この機能は、例えばRaspberry PIのGPIOピンに接続したLEDを点滅させるために活用できます。</p>
<p>フロー内でタイムアウトを作成するのに利用します。メッセージを受け取ると、デフォルトでは<code>payload</code><code>1</code>を設定して送信します。送信後250ms待機し、<code>payload</code><code>0</code>に設定した2つ目のメッセージを送信します。この機能は、例えばRaspberry PiのGPIOピンに接続したLEDを点滅させるために活用できます。</p>
<p>各送信メッセージのペイロードはさまざまな種類の値に設定できます。再送信データなしとすることも可能です。例えば、再送信データを「<i>なし</i>」とし、メッセージを受け取った時に遅延を延長することを選択した場合、triggerードは監視タイマとして動作します。すなわち、指定間隔内にメッセージを受信しない場合にメッセージを送信します。</p>
<p>ペイロードに<i>文字列</i>を指定する場合、mustache形式のテンプレートが利用できます。</p>
<p><code>reset</code>プロパティを持つメッセージを受信した場合、もしくは、<code>payload</code>が設定した値にマッチする場合、仕掛かり中の待機や繰り返しをクリアしメッセージの送信は行いません。</p>

View File

@ -0,0 +1,22 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="http proxy">
<p>プロキシのためのオプション設定</p>
<h3>詳細</h3>
<p>例外ホストに設定したホストにアクセスする際には、プロキシを使用しません。</p>
</script>

View File

@ -59,7 +59,7 @@
<p><code>statusCode</code><code>headers</code>はノードの設定で指定することも可能です。ノードの設定でプロパティを指定した場合には、対応するメッセージプロパティは使用しません。</p>
<h4>クッキーの処理</h4>
<p><code>cookies</code>はキー/値の組からなるオブジェクトとします。値にはデフォルトオプションを使ってクッキーの値として設定する文字列、もしくは、オプションを含むオブジェクトを指定できます。<p>
<p>以下の例では2つのクッキーを設定しています。1つ目は<code>name</code>でその値は<code>nick</code>、2つ目は<code>session</code>で値は<code>1234,</code>、有効期限として15分を指定しています。</p>
<p>以下の例では2つのクッキーを設定しています。1つ目は<code>name</code>でその値は<code>nick</code>、2つ目は<code>session</code>で値は<code>1234</code>、有効期限として15分を指定しています。</p>
<pre>
msg.cookies = {
name: 'nick',

View File

@ -49,7 +49,7 @@
</dl>
<h3>詳細</h3>
<p>ードの設定でurlプロパティを指定する場合、<a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache形式</a>のタグを含めることができます。これにより、URLを入力メッセージの値から構成することができます。例えば、urlが<code>example.com/{{{topic}}}</code>の場合、<code>msg.topic</code>の値による置き換えを自動的に行います。{{{...}}}表記を使うと、/、&といった文字をmustacheがエスケープするのを抑止できます。</p>
<p><b></b>: proxyサーバを利用している場合、環境変数<code>http_proxy=...</code>を設定して、Node-REDを再起動してください。</p>
<p><b></b>: proxyサーバを利用している場合、環境変数<code>http_proxy=...</code>を設定して、Node-REDを再起動するか、あるいはプロキシ設定をしてください。</p>
<h4>複数のHTTPリクエストードの利用</h4>
<p>同一フローで本ノードを複数利用するためには、<code>msg.headers</code>プロパティの扱いに注意しなくてはなりません。例えば、最初のノードがレスポンスヘッダにこのプロパティを設定し、次のノードがこのプロパティをリクエストヘッダに利用するというのは一般的には期待する動作ではありません。<code>msg.headers</code>プロパティをード間で変更しないままとすると、2つ目のードで無視されることになります。カスタムヘッダを設定するためには、<code>msg.headers</code>をまず削除もしくは空のオブジェクト<code>{}</code>にリセットします。
<h4>クッキーの扱い</h4>

View File

@ -19,7 +19,7 @@
<p>カンマ区切りでディレクトリおよびファイルのリストを指定します。空白を含む場合は、引用符で"..."のように囲んでください。</p>
<p>Windowsでは、2重バックスラッシュ\\をディレクトリ名に使用します。</p>
<p>実際に変化したファイルのフルパス名を<code>msg.payload</code>に、検知対象リストの文字列を<code>msg.topic</code>に返します。</p>
<p><code>msg.file</code>は変化したファイルのファイル名表します。<code>msg.type</code>は変化した対象の種別(<i>file</i>もしくは<i>directory</i>)を、<code>msg.size</code>はファイルサイズ(バイト数)を表します。</p>
<p><code>msg.file</code>は変化したファイルのファイル名表します。<code>msg.type</code>は変化した対象の種別(<i>file</i>もしくは<i>directory</i>)を、<code>msg.size</code>はファイルサイズ(バイト数)を表します。</p>
<p>Linuxではファイルとして表されるもの<i>全て</i>が、検知対象にできます。</p>
<p><b>注: </b>検知対象のディレクトリもしくはファイルは存在していなくてはなりません。対象ファイルもしくはディレクトリが削除された場合、再作成されても検知対象から外れたままです。</p>
</script>

View File

@ -311,13 +311,13 @@
"title": "タイトル",
"body": "本文"
},
"tip": "注釈: 本文は<a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GithubのMarkdown形式</a>として整形されます。"
"tip": "注釈: 本文は<a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GitHubのMarkdown形式</a>として整形されます。"
},
"unknown": {
"label": {
"unknown": "unknown"
},
"tip": "<p>現在のNode-RED環境では、本ードの型が不明です。</p><p><i>現在の状態で本ノードをデプロイすると設定は保存されますが、不明なノードがインストールされるまでフローは実行されません。</i></p><p>詳細はノードの「情報」を参照してください。</p>"
"tip": "<p>現在のNode-RED環境では、本ードの型が不明です。</p><p><i>現在の状態で本ノードをデプロイすると設定は保存されますが、不明なノードがインストールされるまでフローは実行されません。</i></p><p>詳細はノードの「情報」を参照してください。</p>"
},
"mqtt": {
"label": {
@ -382,6 +382,10 @@
"basicauth": "ベーシック認証を使用",
"use-tls": "SSL/TLS接続を有効化",
"tls-config": "TLS設定",
"use-proxy": "プロキシを使用",
"proxy-config": "プロキシ設定",
"use-proxyauth": "プロキシ認証を使用",
"noproxy-hosts": "例外ホスト",
"utf8": "文字列",
"binary": "バイナリバッファ",
"json": "JSON",

View File

@ -301,7 +301,7 @@
"title": "标题",
"body": "主体"
},
"tip": "提示: 主题内容可被格式化为 <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">Github风格的Markdown</a>"
"tip": "提示: 主题内容可被格式化为 <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GitHub风格的Markdown</a>"
},
"unknown": {
"label": {

View File

@ -21,6 +21,7 @@ var fs = require("fs");
var registry = require("./registry");
var library = require("./library");
var log;
var exec;
var events;
@ -36,6 +37,7 @@ function init(runtime) {
events = runtime.events;
settings = runtime.settings;
log = runtime.log;
exec = runtime.exec;
}
var activePromise = Promise.resolve();
@ -104,19 +106,19 @@ function installModule(module,version) {
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var args = ['install','--save','--save-prefix="~"','--production',installName];
log.trace(npmCommand + JSON.stringify(args));
var child = child_process.spawn(npmCommand,args,{
cwd: installDir,
shell: true
});
var output = "";
child.stdout.on('data', (data) => {
output += data;
});
child.stderr.on('data', (data) => {
output += data;
});
child.on('close', (code) => {
if (code !== 0) {
exec.run(npmCommand,args,{
cwd: installDir
}, true).then(result => {
if (!isUpgrade) {
log.info(log._("server.install.installed",{name:module}));
resolve(require("./index").addModule(module).then(reportAddedModules));
} else {
log.info(log._("server.install.upgraded",{name:module, version:version}));
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
resolve(require("./registry").setModulePendingUpdated(module,version));
}
}).catch(result => {
var output = result.stderr;
var e;
var lookFor404 = new RegExp(" 404 .*"+module,"m");
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
@ -137,17 +139,7 @@ function installModule(module,version) {
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
} else {
if (!isUpgrade) {
log.info(log._("server.install.installed",{name:module}));
resolve(require("./index").addModule(module).then(reportAddedModules));
} else {
log.info(log._("server.install.upgraded",{name:module, version:version}));
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
resolve(require("./registry").setModulePendingUpdated(module,version));
}
}
});
})
});
}).catch(err => {
// In case of error, reset activePromise to be resolvable
@ -208,25 +200,21 @@ function uninstallModule(module) {
var args = ['remove','--save',module];
log.trace(npmCommand + JSON.stringify(args));
var child = child_process.execFile(npmCommand,args,
{
cwd: installDir
},
function(err, stdin, stdout) {
if (err) {
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(err.toString());
log.warn("------------------------------------------");
reject(new Error(log._("server.install.uninstall-failed",{name:module})));
} else {
exec.run(npmCommand,args,{
cwd: installDir,
},true).then(result => {
log.info(log._("server.install.uninstalled",{name:module}));
reportRemovedModules(list);
library.removeExamplesDir(module);
resolve(list);
}
}
);
}).catch(result => {
var output = result.stderr;
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output.toString());
log.warn("------------------------------------------");
reject(new Error(log._("server.install.uninstall-failed",{name:module})));
});
});
}).catch(err => {
// In case of error, reset activePromise to be resolvable

View File

@ -42,6 +42,21 @@ function handleRuntimeEvent(event) {
runtime.log.trace("runtime event: "+JSON.stringify(event));
publish("notification/"+event.id,event.payload||{},event.retain);
}
function handleEventLog(event) {
var type = event.payload.type;
var id = event.id;
if (event.payload.data) {
var data = event.payload.data;
if (data.endsWith('\n')) {
data = data.substring(0,data.length-1);
}
var lines = data.split(/\n/);
lines.forEach(line => {
runtime.log.debug((type?("["+type+"] "):"")+line)
})
}
publish("event-log/"+event.id,event.payload||{});
}
function publish(topic,data,retain) {
if (retain) {
@ -64,6 +79,8 @@ var api = module.exports = {
runtime.events.on("runtime-event",handleRuntimeEvent);
runtime.events.removeListener("comms",handleCommsEvent);
runtime.events.on("comms",handleCommsEvent);
runtime.events.removeListener("event-log",handleEventLog);
runtime.events.on("event-log",handleEventLog);
},
/**

View File

@ -0,0 +1,69 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
const child_process = require('child_process');
const { util } = require('@node-red/util');
var events;
function logLines(id,type,data) {
events.emit("event-log", {id:id,payload:{ts: Date.now(),data:data,type:type}});
}
module.exports = {
init: function(_runtime) {
events = _runtime.events;
},
run: function(command,args,options,emit) {
var invocationId = util.generateId();
emit && events.emit("event-log", {ts: Date.now(),id:invocationId,payload:{ts: Date.now(),data:command+" "+args.join(" ")}});
return new Promise((resolve, reject) => {
let stdout = "";
let stderr = "";
const child = child_process.spawn(command,args,options);
child.stdout.on('data', (data) => {
const str = ""+data;
stdout += str;
emit && logLines(invocationId,"out",str);
});
child.stderr.on('data', (data) => {
const str = ""+data;
stderr += str;
emit && logLines(invocationId,"err",str);
});
child.on('error', function(err) {
stderr = err.toString();
emit && logLines(invocationId,"err",stderr);
})
child.on('close', (code) => {
let result = {
code: code,
stdout: stdout,
stderr: stderr
}
emit && events.emit("event-log", {id:invocationId,payload:{ts: Date.now(),data:"rc="+code}});
if (code === 0) {
resolve(result)
} else {
reject(result);
}
});
})
}
}

View File

@ -23,6 +23,7 @@ var storage = require("./storage");
var library = require("./library");
var events = require("./events");
var settings = require("./settings");
var exec = require("./exec");
var express = require("express");
var path = require('path');
@ -71,6 +72,7 @@ function init(userSettings,_redUtil,_adminApi) {
redNodes.init(runtime);
library.init(runtime);
externalAPI.init(runtime);
exec.init(runtime);
}
var version;
@ -248,6 +250,7 @@ var runtime = {
events: events,
nodes: redNodes,
library: library,
exec: exec,
util: require("@node-red/util").util,
get adminApi() { return adminApi },
get nodeApp() { return nodeApp },

View File

@ -104,7 +104,7 @@ function load() {
try {
plugin = require("./"+plugins[pluginName].module);
} catch(err) {
return reject(new Error(log._("context.error-loading-module", {module:plugins[pluginName].module,message:err.toString()})));
return reject(new Error(log._("context.error-loading-module2", {module:plugins[pluginName].module,message:err.toString()})));
}
} else {
// Assume `module` is an already-required module we can use
@ -123,7 +123,7 @@ function load() {
}
log.info(log._("context.log-store-init", {name:pluginName, info:"module="+moduleInfo}));
} catch(err) {
return reject(new Error(log._("context.error-loading-module",{module:pluginName,message:err.toString()})));
return reject(new Error(log._("context.error-loading-module2",{module:pluginName,message:err.toString()})));
}
} else {
// Plugin does not specify a 'module'

View File

@ -15,8 +15,9 @@
**/
var when = require('when');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var exec = require("../../../../exec");
var authResponseServer = require('./authServer').ResponseServer;
var sshResponseServer = require('./authServer').ResponseSSHServer;
var clone = require('clone');
@ -26,26 +27,16 @@ var gitCommand = "git";
var gitVersion;
var log;
function runGitCommand(args,cwd,env) {
function runGitCommand(args,cwd,env,emit) {
log.trace(gitCommand + JSON.stringify(args));
return when.promise(function(resolve,reject) {
args.unshift("credential.helper=")
args.unshift("-c");
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env});
var stdout = "";
var stderr = "";
child.stdout.on('data', function(data) {
stdout += data;
});
child.stderr.on('data', function(data) {
stderr += data;
});
child.on('error', function(err) {
stderr = err.toString();
})
child.on('close', function(code) {
if (code !== 0) {
return exec.run(gitCommand, args, {cwd:cwd, env:env}, emit).then(result => {
return result.stdout;
}).catch(result => {
var err = new Error(stderr);
var stdout = result.stdout;
var stderr = result.stderr;
err.stdout = stdout;
err.stderr = stderr;
if (/Connection refused/i.test(stderr)) {
@ -83,26 +74,23 @@ function runGitCommand(args,cwd,env) {
} else if (/name consists only of disallowed characters/i.test(stderr)) {
err.code = "git_missing_user";
}
return reject(err);
}
resolve(stdout);
});
});
throw err;
})
}
function runGitCommandWithAuth(args,cwd,auth) {
function runGitCommandWithAuth(args,cwd,auth,emit) {
return authResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env);
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
return runGitCommand(args,cwd,commandEnv).finally(function() {
return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
rs.close();
});
})
}
function runGitCommandWithSSHCommand(args,cwd,auth) {
function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
return sshResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env);
commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
@ -116,7 +104,7 @@ function runGitCommandWithSSHCommand(args,cwd,auth) {
// GIT_SSH_COMMAND - added in git 2.3.0
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
// console.log('commandEnv:', commandEnv);
return runGitCommand(args,cwd,commandEnv).finally(function() {
return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
rs.close();
});
})
@ -448,13 +436,13 @@ module.exports = {
var promise;
if (auth) {
if ( auth.key_path ) {
promise = runGitCommandWithSSHCommand(args,cwd,auth);
promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
}
else {
promise = runGitCommandWithAuth(args,cwd,auth);
promise = runGitCommandWithAuth(args,cwd,auth,true);
}
} else {
promise = runGitCommand(args,cwd)
promise = runGitCommand(args,cwd,undefined,true)
}
return promise;
// .catch(function(err) {
@ -485,13 +473,13 @@ module.exports = {
var promise;
if (auth) {
if ( auth.key_path ) {
promise = runGitCommandWithSSHCommand(args,cwd,auth);
promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
}
else {
promise = runGitCommandWithAuth(args,cwd,auth);
promise = runGitCommandWithAuth(args,cwd,auth,true);
}
} else {
promise = runGitCommand(args,cwd)
promise = runGitCommand(args,cwd,undefined,true)
}
return promise.catch(function(err) {
if (err.code === 'git_error') {
@ -517,13 +505,13 @@ module.exports = {
args.push(".");
if (auth) {
if ( auth.key_path ) {
return runGitCommandWithSSHCommand(args,cwd,auth);
return runGitCommandWithSSHCommand(args,cwd,auth,true);
}
else {
return runGitCommandWithAuth(args,cwd,auth);
return runGitCommandWithAuth(args,cwd,auth,true);
}
} else {
return runGitCommand(args,cwd);
return runGitCommand(args,cwd,undefined,true);
}
},
getStatus: getStatus,

View File

@ -30,11 +30,10 @@
"installed": "Installed module: __name__",
"install-failed": "Install failed",
"install-failed-long": "Installation of module __name__ failed:",
"install-failed-not-found": "$t(install-failed-long) module not found",
"install-failed-not-found": "$t(server.install.install-failed-long) module not found",
"upgrading": "Upgrading module: __name__ to version: __version__",
"upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version",
"upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found",
"install-failed-not-found": "$t(server.install.install-failed-long) module not found",
"uninstalling": "Uninstalling module: __name__",
"uninstall-failed": "Uninstall failed",
"uninstall-failed-long": "Uninstall of module __name__ failed:",
@ -160,12 +159,12 @@
"context": {
"log-store-init": "Context store : '__name__' [__info__]",
"error-loading-module": "Error loading context store '__module__': __message__ ",
"error-loading-module": "Error loading context store: __message__",
"error-loading-module2": "Error loading context store '__module__': __message__",
"error-module-not-defined": "Context store '__storage__' missing 'module' option",
"error-invalid-module-name": "Invalid context store name: '__name__'",
"error-invalid-default-module": "Default context store unknown: '__storage__'",
"unknown-store": "Unknown context store '__name__' specified. Using default store.",
"error-loading-module": "Error loading context store: __message__",
"localfilesystem": {
"error-circular": "Context __scope__ contains a circular reference that cannot be persisted",
"error-write": "Error writing context: __message__"

View File

@ -8,7 +8,6 @@
"httpStatic": "HTTP Static : __path__"
}
},
"server": {
"loading": "パレットノードのロード",
"palette-editor": {
@ -26,15 +25,14 @@
"removed-types": "削除したノード:",
"install": {
"invalid": "不正なモジュール名",
"installing": "モジュール__name__, バージョン: __version__をインストールします",
"installing": "モジュール__name__, バージョン: __version__ をインストールします",
"installed": "モジュール __name__ をインストールしました",
"install-failed": "インストールに失敗しました",
"install-failed-long": "モジュール __name__ のインストールに失敗しました:",
"install-failed-not-found": "$t(install-failed-long) モジュールが見つかりません",
"install-failed-not-found": "$t(server.install.install-failed-long) モジュールが見つかりません",
"upgrading": "モジュール __name__ をバージョン __version__ に更新します",
"upgraded": "モジュール __name__ を更新しました。新しいバージョンを使うには、Node-REDを再起動してください。",
"upgrade-failed-not-found": "$t(server.install.install-failed-long) バージョンが見つかりません",
"install-failed-not-found": "$t(server.install.install-failed-long) モジュールが見つかりません",
"uninstalling": "モジュールをアンインストールします: __name__",
"uninstall-failed": "アンインストールに失敗しました",
"uninstall-failed-long": "モジュール __name__ のアンインストールに失敗しました:",
@ -49,7 +47,6 @@
"headless-mode": "ヘッドレスモードで実行中です",
"httpadminauth-deprecated": "httpAdminAuthは非推奨です。代わりに adminAuth を使用してください"
},
"api": {
"flows": {
"error-save": "フローの保存エラー: __message__",
@ -67,27 +64,25 @@
"error-enable": "ノードの有効化に失敗しました:"
}
},
"comms": {
"error": "通信チャネルエラー: __message__",
"error-server": "サーバエラー: __message__",
"error-send": "送信エラー: __message__"
},
"settings": {
"user-not-available": "ユーザ設定を保存できません: __message__",
"not-available": "設定が利用できません",
"property-read-only": "プロパティ '__prop__' は読み出し専用です"
},
"nodes": {
"credentials": {
"error":"クレデンシャルの読み込みエラー: __message__",
"error-saving":"クレデンシャルの保存エラー: __message__",
"error": "クレデンシャルの読み込みエラー: __message__",
"error-saving": "クレデンシャルの保存エラー: __message__",
"not-registered": "クレデンシャル '__type__' は登録されていません",
"system-key-warning": "\n\n---------------------------------------------------------------------\nフローのクレデンシャルファイルはシステム生成キーで暗号化されています。\n\nシステム生成キーを何らかの理由で失った場合、クレデンシャルファイルを\n復元することはできません。その場合、ファイルを削除してクレデンシャルを\n再入力しなければなりません。\n\n設定ファイル内で 'credentialSecret' オプションを使って独自キーを設定\nします。変更を次にデプロイする際、Node-REDは選択したキーを用いてクレ\nデンシャルを再暗号化します。 \n\n---------------------------------------------------------------------\n"
},
"flows": {
"safe-mode": "セーフモードでフローを停止しました。開始するためにはデプロイしてください",
"registered-missing": "欠落しているノードを登録します: __type__",
"error": "フローの読み込みエラー: __message__",
"starting-modified-nodes": "更新されたノードを開始します",
@ -128,7 +123,6 @@
}
}
},
"storage": {
"index": {
"forbidden-flow-name": "不正なフロー名"
@ -156,14 +150,17 @@
}
}
},
"context": {
"log-store-init": "コンテクストストア : '__name__' [__info__]",
"error-loading-module": "コンテクストストア '__module__' のロードでエラーが発生しました: __message__ ",
"error-module-not-defined": "コンテクストストア '__storage__' に 'module' オプションが指定されていません",
"error-invalid-module-name": "不正なコンテクストストア名: '__name__'",
"error-invalid-default-module": "デフォルトコンテクストストアが不明: '__storage__'",
"unknown-store": "不明なコンテクストストア '__name__' が指定されました。デフォルトストアを使用します。"
"log-store-init": "コンテキストストア : '__name__' [__info__]",
"error-loading-module": "コンテキストストアのロードでエラーが発生しました: __message__",
"error-loading-module2": "コンテキストストア '__module__' のロードでエラーが発生しました: __message__",
"error-module-not-defined": "コンテキストストア '__storage__' に 'module' オプションが指定されていません",
"error-invalid-module-name": "不正なコンテキストストア名: '__name__'",
"error-invalid-default-module": "デフォルトコンテキストストアが不明: '__storage__'",
"unknown-store": "不明なコンテキストストア '__name__' が指定されました。デフォルトストアを使用します。",
"localfilesystem": {
"error-circular": "コンテキスト __scope__ は永続化できない循環参照を含んでいます",
"error-write": "コンテキスト書込みエラー: __message__"
}
}
}

View File

@ -30,19 +30,22 @@ module.exports = {
log.init(settings);
i18n.init();
},
/**
* Logging utilities
* @see module:@node-red/util.module:log
*/
log: log,
/**
* Internationalization utilities
* @see module:@node-red/util.module:i18n
*/
i18n: i18n,
/**
* General utilities
* @see module:@node-red/util.module:util
*/
util: util
util: util,
}

View File

@ -38,18 +38,6 @@ function checkVersion(userSettings) {
}
}
function checkBuild() {
console.log("Skipping red.js/checkBuild");
// var editorFile = path.resolve(path.join(__dirname,"..","..","..","..","public","red","red.min.js"));
// try {
// var stats = fs.statSync(editorFile);
// } catch(err) {
// var e = new Error("Node-RED not built");
// e.code = "not_built";
// throw e;
// }
}
module.exports = {
init: function(httpServer,userSettings) {
if (!userSettings) {
@ -59,7 +47,6 @@ module.exports = {
if (!userSettings.SKIP_BUILD_CHECK) {
checkVersion(userSettings);
checkBuild();
}
if (!userSettings.coreNodesDir) {

View File

@ -148,7 +148,7 @@ module.exports = {
// The following property can be used to cause insecure HTTP connections to
// be redirected to HTTPS.
//requireHttps: true
//requireHttps: true,
// The following property can be used to disable the editor. The admin API
// is not affected by this option. To disable both the editor and the admin
@ -181,6 +181,11 @@ module.exports = {
// next();
//},
// The following property can be used to pass custom options to the Express.js
// server used by Node-RED. For a full list of available options, refer
// to http://expressjs.com/en/api.html#app.settings.table
//httpServerOptions: { },
// The following property can be used to verify websocket connection attempts.
// This allows, for example, the HTTP request headers to be checked to ensure
// they include valid authentication information.
@ -256,5 +261,5 @@ module.exports = {
// To enable the Projects feature, set this value to true
enabled: false
}
},
}
}

View File

@ -21,7 +21,7 @@ var fs = require('fs-extra');
var path = require('path');
var app = express();
var RED = require("../../red/red.js");
var RED = require("nr-test-utils").require("node-red/lib/red.js");
var utilPage = require("./pageobjects/util/util_page");

View File

@ -16,7 +16,7 @@
var when = require("when");
var events = require("../../../../red/runtime/events.js");
var events = require("nr-test-utils").require("@node-red/runtime/lib/events.js");
var palette = require("./palette_page");
var nodeFactory = require("../nodes/nodefactory_page");

View File

@ -29,7 +29,7 @@ debugNode.prototype.setOutput = function(complete) {
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]/button');
if (complete !== 'true') {
// Select the "msg" type.
browser.clickWithWait('/html/body/div[11]/a[1]');
browser.clickWithWait('//div[@class="red-ui-typedInput-options"][1]/a[1]');
// Input the path in msg.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input');
browser.keys(['Control', 'a', 'Control']);

View File

@ -54,8 +54,8 @@ changeNode.prototype.ruleSet = function(p, pt, to, tot, index) {
setT("set", index);
if (pt) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
var num = 5 * index + 6;
var ptXPath = '/html/body/div[' + num + ']/a[' + ptType[pt] + ']';
var num = 5 * (index - 1) + 1;
var ptXPath = '//div[@class="red-ui-typedInput-options"][' + num + ']/a[' + ptType[pt] + ']';
browser.clickWithWait(ptXPath);
}
if (p) {
@ -63,8 +63,8 @@ changeNode.prototype.ruleSet = function(p, pt, to, tot, index) {
}
if (tot) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]');
var num = 5 * index + 7;
var totXPath = '/html/body/div[' + num + ']/a[' + totType[tot] + ']';
var num = 5 * (index - 1) + 2;
var totXPath = '//div[@class="red-ui-typedInput-options"][' + num + ']/a[' + totType[tot] + ']';
browser.clickWithWait(totXPath);
}
if (to) {

View File

@ -315,8 +315,9 @@ describe('cookbook', function() {
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
var message = debugTab.getMessage();
message[1].indexOf('application/json').should.not.eql(-1);
var messages = debugTab.getMessage();
var contents = messages.join([separator = ""]);
contents.indexOf('application/json').should.not.eql(-1);
});
it('serve a local file', function () {

View File

@ -365,6 +365,138 @@ describe('exec node', function() {
});
});
it('should preserve existing properties on msg object', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:false, append:"", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
// arg3(error,stdout,stderr);
arg3(null,arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null,null];
var completeTest = function() {
received = received + 1;
if (received < 3) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("echo");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg.should.have.property("foo","bar");
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ECHO");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg.should.have.property("foo","bar");
msg = messages[2];
msg.should.have.property("payload");
msg.payload.should.have.property("code",0);
msg.should.have.property("foo","bar");
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[2] = msg;
completeTest();
});
n1.receive({payload:"and", foo:"bar"});
});
});
it('should preserve existing properties on msg object for a failing command', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"error", addpay:false, append:"", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
// arg3(error,stdout,stderr);
arg3({code: 1},arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null,null];
var completeTest = function() {
received++;
if (received < 3) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("error");
msg.should.have.property("rc");
msg.rc.should.have.property("code",1);
msg.rc.should.have.property("message",undefined);
msg.should.have.property("foo",null);
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ERROR");
msg.should.have.property("foo",null);
msg = messages[2];
msg.should.have.property("payload");
msg.payload.should.have.property("code",1);
msg.should.have.property("foo",null);
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[2] = msg;
completeTest();
});
n1.receive({payload:"and", foo:null});
});
});
});
describe('calling spawn', function() {
@ -672,5 +804,116 @@ describe('exec node', function() {
});
});
it('should preserve existing properties on msg object', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"cmd /C echo this now works", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "this now works\r\n";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo this now works", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "this now works\n";
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null];
var completeTest = function() {
received++;
if (received < 2) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal(expected);
msg.should.have.property("foo",123);
msg = messages[1];
msg.should.have.property("payload");
should.exist(msg.payload);
msg.payload.should.have.property("code",0);
msg.should.have.property("foo",123);
done();
}
catch(err) {
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n1.receive({payload:null,foo:123});
});
});
it('should preserve existing properties on msg object for a failing command', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
// Cannot use mkdir because Windows mkdir command automatically creates non-existent directories.
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "IP address must be specified.";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"mkdir /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = ' directory';
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null];
var completeTest = function() {
received++;
if (received < 2) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.should.have.property("foo","baz");
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.have.property("code",1);
msg.should.have.property("foo","baz");
done();
}
catch(err) {
done(err);
}
};
n3.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n1.receive({payload:null,foo:"baz"});
});
});
});
});

View File

@ -24,6 +24,7 @@ var stoppable = require('stoppable');
var helper = require("node-red-node-test-helper");
var httpRequestNode = require("nr-test-utils").require("@node-red/nodes/core/io/21-httprequest.js");
var tlsNode = require("nr-test-utils").require("@node-red/nodes/core/io/05-tls.js");
var httpProxyNode = require("nr-test-utils").require("@node-red/nodes/core/io/06-httpproxy.js");
var hashSum = require("hash-sum");
var httpProxy = require('http-proxy');
var cookieParser = require('cookie-parser');
@ -1194,6 +1195,80 @@ describe('HTTP Request Node', function() {
n1.receive({payload:"foo"});
});
});
it('should use http-proxy-config', function(done) {
var flow = [
{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"},
{id:"n2",type:"helper"},
{id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort}
];
var testNode = [ httpRequestNode, httpProxyNode ];
deleteProxySetting();
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.should.have.property('headers');
msg.payload.headers.should.have.property('x-testproxy-header','foobar');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should not use http-proxy-config when invalid url is specified', function(done) {
var flow = [
{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"},
{id:"n2", type:"helper"},
{id:"n3",type:"http proxy",url:"invalidvalue"}
];
var testNode = [ httpRequestNode, httpProxyNode ];
deleteProxySetting();
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.should.have.property('headers');
msg.payload.headers.should.not.have.property('x-testproxy-header','foobar');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should use http-proxy-config when valid noproxy is specified', function(done) {
var flow = [
{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"},
{id:"n2", type:"helper"},
{id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort,noproxy:["foo","localhost"]}
];
var testNode = [ httpRequestNode, httpProxyNode ];
deleteProxySetting();
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.headers.should.not.have.property('x-testproxy-header','foobar');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
});
describe('authentication', function() {
@ -1264,6 +1339,63 @@ describe('HTTP Request Node', function() {
n1.receive({payload:"foo"});
});
});
it('should authenticate on proxy server(http-proxy-config)', function(done) {
var flow = [
{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate'),proxy:"n3"},
{id:"n2", type:"helper"},
{id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort}
];
var testNode = [ httpRequestNode, httpProxyNode ];
deleteProxySetting();
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
n3.credentials = {username:'foouser', password:'barpassword'};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.should.have.property('user', 'foouser');
msg.payload.should.have.property('pass', 'barpassword');
msg.payload.should.have.property('headers');
msg.payload.headers.should.have.property('x-testproxy-header','foobar');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should output an error when proxy authentication was failed(http-proxy-config)', function(done) {
var flow = [
{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate'),proxy:"n3"},
{id:"n2", type:"helper"},
{id:"n3",type:"http proxy",url:"http://@localhost:" + testProxyPort}
];
var testNode = [ httpRequestNode, httpProxyNode ];
deleteProxySetting();
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
n3.credentials = {username:'xxxuser', password:'barpassword'};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',407);
msg.headers.should.have.property('proxy-authenticate', 'BASIC realm="test"');
msg.payload.should.have.property('headers');
msg.payload.headers.should.have.property('x-testproxy-header','foobar');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
});
describe('redirect-cookie', function() {

View File

@ -540,6 +540,93 @@ describe('file Nodes', function() {
});
});
it('should write to multiple files', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, "createDir":true, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
var tmp_path = path.join(resourcesDir, "tmp");
var len = 1024*1024*10;
var file_count = 5;
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
var n2 = helper.getNode("helperNode1");
var count = 0;
n2.on("input", function(msg) {
try {
count++;
if (count == file_count) {
for(var i = 0; i < file_count; i++) {
var name = path.join(tmp_path, String(i));
var f = fs.readFileSync(name);
f.should.have.length(len);
f[0].should.have.equal(i);
}
fs.removeSync(tmp_path);
done();
}
}
catch (e) {
try {
fs.removeSync(tmp_path);
}
catch (e1) {
}
done(e);
}
});
for(var i = 0; i < file_count; i++) {
var data = new Buffer(len);
data.fill(i);
var name = path.join(tmp_path, String(i));
var msg = {payload:data, filename:name};
n1.receive(msg);
}
});
});
it('should write to multiple files if node is closed', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, "createDir":true, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
var tmp_path = path.join(resourcesDir, "tmp");
var len = 1024*1024*10;
var file_count = 5;
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
var n2 = helper.getNode("helperNode1");
var count = 0;
n2.on("input", function(msg) {
try {
count++;
if (count == file_count) {
for(var i = 0; i < file_count; i++) {
var name = path.join(tmp_path, String(i));
var f = fs.readFileSync(name);
f.should.have.length(len);
f[0].should.have.equal(i);
}
fs.removeSync(tmp_path);
done();
}
}
catch (e) {
try {
fs.removeSync(tmp_path);
}
catch (e1) {
}
done(e);
}
});
for(var i = 0; i < file_count; i++) {
var data = new Buffer(len);
data.fill(i);
var name = path.join(tmp_path, String(i));
var msg = {payload:data, filename:name};
n1.receive(msg);
}
n1.close();
});
});
});

View File

@ -94,6 +94,29 @@ describe("api/auth/tokens", function() {
});
});
});
it('returns a valid api token', function(done) {
Tokens.init({
tokens: [{
token: "1234",
user: "fred",
}]
},{
getSessions:function() {
return when.resolve({});
}
}).then(function() {
Tokens.get("1234").then(function(token) {
try {
token.should.have.a.property("user","fred");
done();
} catch(err) {
done(err);
}
});
});
});
});
describe("#create",function() {

View File

@ -91,6 +91,30 @@ describe("api/editor/locales", function() {
done();
});
});
it('returns for locale defined only with primary tag ', function(done) {
var orig = i18n.i.getResourceBundle;
i18n.i.getResourceBundle = function (lang, ns) {
if (lang === "ja-JP") {
return undefined;
}
return orig(lang, ns);
};
request(app)
// returns `ja` instead of `ja-JP`
.get("/locales/message-catalog?lng=ja-JP")
.expect(200)
.end(function(err,res) {
i18n.i.getResourceBundle = orig;
if (err) {
return done(err);
}
res.body.should.have.property('namespace','message-catalog');
res.body.should.have.property('lang','ja');
done();
});
});
});
// describe('get all node resource catalogs',function() {

View File

@ -26,46 +26,52 @@ var NR_TEST_UTILS = require("nr-test-utils");
var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme");
describe("api/editor/theme", function() {
beforeEach(function() {
sinon.stub(fs,"statSync",function() { return true; });
describe("api/editor/theme", function () {
beforeEach(function () {
sinon.stub(fs, "statSync", function () { return true; });
});
afterEach(function() {
theme.init({settings:{}});
afterEach(function () {
theme.init({settings: {}});
fs.statSync.restore();
});
it("applies the default theme", function() {
it("applies the default theme", function () {
var result = theme.init({});
should.not.exist(result);
var context = theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("title","Node-RED");
context.page.should.have.a.property("favicon","favicon.ico");
context.page.should.have.a.property("title", "Node-RED");
context.page.should.have.a.property("favicon", "favicon.ico");
context.page.should.have.a.property("tabicon", "red/images/node-red-icon-black.svg");
context.should.have.a.property("header");
context.header.should.have.a.property("title","Node-RED");
context.header.should.have.a.property("image","red/images/node-red.png");
context.header.should.have.a.property("title", "Node-RED");
context.header.should.have.a.property("image", "red/images/node-red.png");
context.should.have.a.property("asset");
context.asset.should.have.a.property("red", "red/red.min.js");
context.asset.should.have.a.property("main", "red/main.min.js");
should.not.exist(theme.settings());
});
it("picks up custom theme", function() {
it("picks up custom theme", function () {
theme.init({
editorTheme: {
page: {
title: "Test Page Title",
favicon: "/absolute/path/to/theme/icon",
favicon: "/absolute/path/to/theme/favicon",
tabicon: "/absolute/path/to/theme/tabicon",
css: "/absolute/path/to/custom/css/file.css",
scripts: "/absolute/path/to/script.js"
},
header: {
title: "Test Header Title",
url: "http://nodered.org",
image: "/absolute/path/to/header/image" // or null to remove image
},
deployButton: {
type:"simple",
label:"Save",
type: "simple",
label: "Save",
icon: "/absolute/path/to/deploy/button/image" // or null to remove image
},
@ -83,6 +89,16 @@ describe("api/editor/theme", function() {
login: {
image: "/absolute/path/to/login/page/big/image" // a 256x256 image
},
palette: {
editable: true,
catalogues: ['https://catalogue.nodered.org/catalogue.json'],
theme: [{ category: ".*", type: ".*", color: "#f0f" }]
},
projects: {
enabled: false
}
}
});
@ -91,22 +107,40 @@ describe("api/editor/theme", function() {
var context = theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("title","Test Page Title");
context.page.should.have.a.property("title", "Test Page Title");
context.page.should.have.a.property("favicon", "theme/favicon/favicon");
context.page.should.have.a.property("tabicon", "theme/tabicon/tabicon");
context.should.have.a.property("header");
context.header.should.have.a.property("title","Test Header Title");
context.header.should.have.a.property("title", "Test Header Title");
context.header.should.have.a.property("url", "http://nodered.org");
context.header.should.have.a.property("image", "theme/header/image");
context.page.should.have.a.property("css");
context.page.css.should.have.lengthOf(1);
context.page.css[0].should.eql('theme/css/file.css');
context.page.should.have.a.property("scripts");
context.page.scripts.should.have.lengthOf(1);
context.page.scripts[0].should.eql('theme/scripts/script.js');
context.should.have.a.property("login");
context.login.should.have.a.property("image", "theme/login/image");
var settings = theme.settings();
settings.should.have.a.property("deployButton");
settings.deployButton.should.have.a.property("type", "simple");
settings.deployButton.should.have.a.property("label", "Save");
settings.deployButton.should.have.a.property("icon", "theme/deploy/image");
settings.should.have.a.property("userMenu");
settings.userMenu.should.be.eql(false);
settings.should.have.a.property("menu");
settings.menu.should.have.a.property("menu-item-import-library", false);
settings.menu.should.have.a.property("menu-item-export-library", false);
settings.menu.should.have.a.property("menu-item-keyboard-shortcuts", false);
settings.menu.should.have.a.property("menu-item-help", { label: "Alternative Help Link Text", url: "http://example.com" });
settings.should.have.a.property("palette");
settings.palette.should.have.a.property("editable", true);
settings.palette.should.have.a.property("catalogues", ['https://catalogue.nodered.org/catalogue.json']);
settings.palette.should.have.a.property("theme", [{ category: ".*", type: ".*", color: "#f0f" }]);
settings.should.have.a.property("projects");
settings.projects.should.have.a.property("enabled", false);
});
});

View File

@ -21,9 +21,6 @@ var path = require("path");
var fs = require('fs');
var EventEmitter = require('events');
var child_process = require('child_process');
var NR_TEST_UTILS = require("nr-test-utils");
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
@ -42,16 +39,21 @@ describe('nodes/registry/installer', function() {
_: function() { return "abc"}
}
before(function() {
installer.init({log:mockLog, settings:{}, events: new EventEmitter()});
beforeEach(function() {
installer.init({log:mockLog, settings:{}, events: new EventEmitter(), exec: {
run: function() {
return Promise.resolve("");
}
}});
});
function initInstaller(execResult) {
installer.init({log:mockLog, settings:{}, events: new EventEmitter(), exec: {
run: function() {
return execResult;
}
}});
}
afterEach(function() {
if (child_process.spawn.restore) {
child_process.spawn.restore();
}
if (child_process.execFile.restore) {
child_process.execFile.restore();
}
if (registry.addModule.restore) {
registry.addModule.restore();
}
@ -76,39 +78,33 @@ describe('nodes/registry/installer', function() {
describe("installs module", function() {
it("rejects when npm returns a 404", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.stderr.emit('data'," 404 this_wont_exist");
ee.emit('close',1);
},10)
return ee;
});
var res = {
code: 1,
stdout:"",
stderr:" 404 this_wont_exist"
}
var p = Promise.reject(res);
p.catch((err)=>{});
initInstaller(p)
installer.installModule("this_wont_exist").catch(function(err) {
err.should.have.property("code",404);
done();
});
});
it("rejects when npm does not find specified version", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.stderr.emit('data'," version not found: this_wont_exist@0.1.2");
ee.emit('close',1);
},10)
return ee;
});
var res = {
code: 1,
stdout:"",
stderr:" version not found: this_wont_exist@0.1.2"
}
var p = Promise.reject(res);
p.catch((err)=>{});
initInstaller(p)
sinon.stub(typeRegistry,"getModuleInfo", function() {
return {
version: "0.1.1"
}
});
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql(404);
done();
@ -126,17 +122,14 @@ describe('nodes/registry/installer', function() {
});
});
it("rejects with generic error", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt,cb) {
var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.stderr.emit('data'," kaboom!");
ee.emit('close',1);
},10)
return ee;
});
var res = {
code: 1,
stdout:"",
stderr:" kaboom!"
}
var p = Promise.reject(res);
p.catch((err)=>{});
initInstaller(p)
installer.installModule("this_wont_exist").then(function() {
done(new Error("Unexpected success"));
}).catch(function(err) {
@ -145,15 +138,16 @@ describe('nodes/registry/installer', function() {
});
it("succeeds when module is found", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.emit('close',0);
},10)
return ee;
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
initInstaller(p)
var addModule = sinon.stub(registry,"addModule",function(md) {
return when.resolve(nodeInfo);
});
@ -191,15 +185,15 @@ describe('nodes/registry/installer', function() {
return when.resolve(nodeInfo);
});
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule"));
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.emit('close',0);
},10)
return ee;
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
initInstaller(p)
installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo);
done();
@ -226,9 +220,14 @@ describe('nodes/registry/installer', function() {
var removeModule = sinon.stub(registry,"removeModule",function(md) {
return when.resolve(nodeInfo);
});
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) {
cb(new Error("test_error"),"","");
});
var res = {
code: 1,
stdout:"",
stderr:"error"
}
var p = Promise.reject(res);
p.catch((err)=>{});
initInstaller(p)
installer.uninstallModule("this_wont_exist").then(function() {
done(new Error("Unexpected success"));
@ -244,9 +243,14 @@ describe('nodes/registry/installer', function() {
var getModuleInfo = sinon.stub(registry,"getModuleInfo",function(md) {
return {nodes:[]};
});
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) {
cb(null,"","");
});
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
initInstaller(p)
sinon.stub(fs,"statSync", function(fn) { return {}; });

View File

@ -0,0 +1,22 @@
/**
* 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 path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var exec = NR_TEST_UTILS.require("@node-red/runtime/lib/exec");