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/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/editor.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.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/tray.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.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", "packages/node_modules/@node-red/editor-client/src/js/ui/library.js",

View File

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

View File

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

View File

@ -32,6 +32,8 @@ var sessions = {};
var loadedSessions = null; var loadedSessions = null;
var apiAccessTokens;
function expireSessions() { function expireSessions() {
var now = Date.now(); var now = Date.now();
var modified = false; var modified = false;
@ -67,16 +69,33 @@ module.exports = {
// At this point, storage will not have been initialised, so defer loading // At this point, storage will not have been initialised, so defer loading
// the sessions until there's a request for them. // the sessions until there's a request for them.
loadedSessions = null; 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(); return Promise.resolve();
}, },
get: function(token) { get: function(token) {
return loadSessions().then(function() { return loadSessions().then(function() {
if (sessions[token]) { var info = apiAccessTokens[token] || null;
if (sessions[token].expires < Date.now()) {
return expireSessions().then(function() { return 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]);
} }
return Promise.resolve(sessions[token]);
}); });
}, },
create: function(user,client,scope) { 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("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,apiUtil.errorHandler); 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; var runtimeAPI;
function loadResource(lang, namespace) {
var catalog = i18n.i.getResourceBundle(lang, namespace);
if (!catalog) {
var parts = lang.split("-");
if (parts.length == 2) {
var new_lang = parts[0];
return i18n.i.getResourceBundle(new_lang, namespace);
}
}
return catalog;
}
module.exports = { module.exports = {
init: function(_runtimeAPI) { init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI; runtimeAPI = _runtimeAPI;
@ -33,7 +45,7 @@ module.exports = {
var prevLang = i18n.i.language; var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default // Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){ i18n.i.changeLanguage(lang, function(){
var catalog = i18n.i.getResourceBundle(lang, namespace); var catalog = loadResource(lang, namespace);
res.json(catalog||{}); res.json(catalog||{});
}); });
i18n.i.changeLanguage(prevLang); i18n.i.changeLanguage(prevLang);

View File

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

View File

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

View File

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

View File

@ -14,11 +14,13 @@
"ctrl-e": "core:show-export-dialog", "ctrl-e": "core:show-export-dialog",
"ctrl-i": "core:show-import-dialog", "ctrl-i": "core:show-import-dialog",
"ctrl-space": "core:toggle-sidebar", "ctrl-space": "core:toggle-sidebar",
"ctrl-p": "core:toggle-palette",
"ctrl-,": "core:show-user-settings", "ctrl-,": "core:show-user-settings",
"ctrl-alt-r": "core:show-remote-diff", "ctrl-alt-r": "core:show-remote-diff",
"ctrl-alt-n": "core:new-project", "ctrl-alt-n": "core:new-project",
"ctrl-alt-o": "core:open-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": { "workspace": {
"backspace": "core:delete-selection", "backspace": "core:delete-selection",

View File

@ -398,6 +398,10 @@ var RED = (function() {
// Refresh flow library to ensure any examples are updated // Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary(); RED.library.loadFlowLibrary();
}); });
RED.comms.subscribe("event-log/#", function(topic,payload) {
var id = topic.substring(9);
RED.eventLog.log(id,payload);
});
} }
function showAbout() { 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")}}} // {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, // 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-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 null
]}); ]});
menuOptions.push(null); menuOptions.push(null);
@ -482,6 +488,7 @@ var RED = (function() {
RED.library.init(); RED.library.init();
RED.keyboard.init(); RED.keyboard.init();
RED.palette.init(); RED.palette.init();
RED.eventLog.init();
if (RED.settings.theme('palette.editable') !== false) { if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init(); RED.palette.editor.init();
} else { } else {

View File

@ -34,14 +34,24 @@ RED.tabs = (function() {
if (options.vertical) { if (options.vertical) {
wrapper.addClass("red-ui-tabs-vertical"); wrapper.addClass("red-ui-tabs-vertical");
} }
if (options.addButton && typeof options.addButton === 'function') { if (options.addButton) {
wrapper.addClass("red-ui-tabs-add"); 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); 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) { addButton.find('a').click(function(evt) {
evt.preventDefault(); evt.preventDefault();
options.addButton(); 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 scrollLeft;
var scrollRight; var scrollRight;

View File

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

View File

@ -498,7 +498,7 @@ RED.diff = (function() {
nodeDiv.css('backgroundColor',colour); nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv); 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; return nodeDiv;
} }

View File

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

View File

@ -199,6 +199,9 @@ RED.editor.types._markdown = (function() {
} }
} else { } else {
editor.session.replace(editor.selection.getRange(), (style.before||"")+current+(style.after||"")); 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(); 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", class: "primary palette-module-install-confirm-button-update",
click: function() { click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true); 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) { installNodeModule(entry.name,version,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { 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); done(xhr);
@ -898,12 +922,35 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-remove", class: "primary palette-module-install-confirm-button-remove",
click: function() { click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true); 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) { removeNodeModule(entry.name, function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { 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(); notification.close();
@ -940,11 +987,36 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-install", class: "primary palette-module-install-confirm-button-install",
click: function() { click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true); 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) { installNodeModule(entry.id,entry.version,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { 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); done(xhr);

View File

@ -20,7 +20,7 @@ RED.palette = (function() {
var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced']; var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
var categoryContainers = {}; var categoryContainers = {};
var sidebarControls;
function createCategory(originalCategory,rootCategory,category,ns) { function createCategory(originalCategory,rootCategory,category,ns) {
if ($("#palette-base-category-"+rootCategory).length === 0) { if ($("#palette-base-category-"+rootCategory).length === 0) {
@ -110,13 +110,26 @@ RED.palette = (function() {
var popOverContent; var popOverContent;
try { try {
var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>"; var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>";
if (label != type) { popOverContent = $('<div></div>').append($(l+(info?info:$("script[data-help-name='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
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())
.filter(function(n) { .filter(function(n) {
return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0) 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) { } catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break // Malformed HTML may cause errors. TODO: need to understand what can break
// NON-NLS: internal debug // NON-NLS: internal debug
@ -129,9 +142,9 @@ RED.palette = (function() {
} }
function setIcon(element,sf) { 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"); 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) { function escapeNodeType(nt) {
@ -169,7 +182,7 @@ RED.palette = (function() {
if (def.icon) { if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def); var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d); 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); 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; var categoryList = coreCategories;
if (RED.settings.paletteCategories) { if (RED.settings.paletteCategories) {
categoryList = 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.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() { function getCategories() {
var categories = []; var categories = [];
$("#palette-container .palette-category").each(function(i,d) { $("#palette-container .palette-category").each(function(i,d) {

View File

@ -870,7 +870,13 @@ RED.sidebar.versionControl = (function() {
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); 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(); var activeProject = RED.projects.getActiveProject();
RED.eventLog.startEvent("Push changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
var url = "projects/"+activeProject.name+"/push"; var url = "projects/"+activeProject.name+"/push";
if (activeProject.git.branches.remoteAlt) { if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt; url+="/"+activeProject.git.branches.remoteAlt;
@ -914,7 +920,13 @@ RED.sidebar.versionControl = (function() {
var pullRemote = function(options) { var pullRemote = function(options) {
options = options || {}; options = options || {};
var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); 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(); var activeProject = RED.projects.getActiveProject();
RED.eventLog.startEvent("Pull changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
var url = "projects/"+activeProject.name+"/pull"; var url = "projects/"+activeProject.name+"/pull";
if (activeProject.git.branches.remoteAlt) { if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt; url+="/"+activeProject.git.branches.remoteAlt;

View File

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

View File

@ -105,6 +105,8 @@ RED.sidebar = (function() {
} }
var sidebarSeparator = {}; var sidebarSeparator = {};
sidebarSeparator.dragging = false;
$("#sidebar-separator").draggable({ $("#sidebar-separator").draggable({
axis: "x", axis: "x",
start:function(event,ui) { start:function(event,ui) {
@ -114,6 +116,7 @@ RED.sidebar = (function() {
sidebarSeparator.start = ui.position.left; sidebarSeparator.start = ui.position.left;
sidebarSeparator.chartWidth = $("#workspace").width(); sidebarSeparator.chartWidth = $("#workspace").width();
sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2; sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2;
sidebarSeparator.dragging = true;
if (!RED.menu.isSelected("menu-item-sidebar")) { if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true; sidebarSeparator.opening = true;
@ -166,6 +169,7 @@ RED.sidebar = (function() {
RED.events.emit("sidebar:resize"); RED.events.emit("sidebar:resize");
}, },
stop:function(event,ui) { stop:function(event,ui) {
sidebarSeparator.dragging = false;
if (sidebarSeparator.closing) { if (sidebarSeparator.closing) {
$("#sidebar").removeClass("closing"); $("#sidebar").removeClass("closing");
RED.menu.setSelected("menu-item-sidebar",false); 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) { function toggleSidebar(state) {
if (!state) { if (!state) {
$("#main-container").addClass("sidebar-closed"); $("#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 table = $('<table class="node-info"></table>').appendTo(propertiesSection.content);
var tableBody = $('<tbody>').appendTo(table); var tableBody = $('<tbody>').appendTo(table);
var subflowNode; var subflowNode;
var subflowUserCount; 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])) $('<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") { if (!m && node.type != "subflow") {
var defaults; var defaults;
if (node.type === 'unknown') { if (node.type === 'unknown') {
@ -218,9 +220,13 @@ RED.sidebar.info = (function() {
}) })
} else if (node._def) { } else if (node._def) {
defaults = node._def.defaults; 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) { if (defaults) {
var count = 0;
for (var n in defaults) { for (var n in defaults) {
if (n != "name" && n != "info" && defaults.hasOwnProperty(n)) { if (n != "name" && n != "info" && defaults.hasOwnProperty(n)) {
var val = node[n]; var val = node[n];
@ -254,9 +260,9 @@ 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 (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 (node.type !== 'tab') {
@ -308,6 +314,20 @@ RED.sidebar.info = (function() {
$(".node-info-property-row").toggle(expandedSections["property"]); $(".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) { 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); 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); nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv); 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) { if (def.inputs > 0) {
$('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv); $('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);

View File

@ -767,10 +767,12 @@ RED.utils = (function() {
return RED.settings.apiRootUrl+"icons/node-red/alert.png" return RED.settings.apiRootUrl+"icons/node-red/alert.png"
} else if (node && node.icon) { } else if (node && node.icon) {
var iconPath = separateIconPath(node.icon); var iconPath = separateIconPath(node.icon);
if (iconPath.module === "font-awesome") { if (isIconExists(iconPath)) {
return node.icon; if (iconPath.module === "font-awesome") {
} else if (isIconExists(iconPath)) { return node.icon;
return RED.settings.apiRootUrl+"icons/" + node.icon; } else {
return RED.settings.apiRootUrl+"icons/" + node.icon;
}
} }
} }
@ -895,10 +897,12 @@ RED.utils = (function() {
/** /**
* Create or update an icon element and append it to iconContainer. * Create or update an icon element and append it to iconContainer.
* @param iconUrl - Url of icon. * @param iconUrl - Url of icon.
* @param iconContainer - icon container element with palette_icon_container class. * @param iconContainer - Icon container element with palette_icon_container class.
* @param isLarge - whether the icon size is large. * @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. // Removes the previous icon when icon was changed.
var iconElement = iconContainer.find(".palette_icon"); var iconElement = iconContainer.find(".palette_icon");
if (iconElement.length !== 0) { if (iconElement.length !== 0) {
@ -912,13 +916,23 @@ RED.utils = (function() {
// Show either icon image or font-awesome icon // Show either icon image or font-awesome icon
var iconPath = separateIconPath(iconUrl); var iconPath = separateIconPath(iconUrl);
if (iconPath.module === "font-awesome") { if (iconPath.module === "font-awesome") {
var faIconElement = $('<i/>').appendTo(iconContainer); var fontAwesomeUnicode = RED.nodes.fontAwesome.getIconUnicode(iconPath.file);
var faLarge = isLarge ? "fa-lg " : ""; if (fontAwesomeUnicode) {
faIconElement.addClass("palette_icon_fa fa fa-fw " + faLarge + iconPath.file); var faIconElement = $('<i/>').appendTo(iconContainer);
} else { var faLarge = isLarge ? "fa-lg " : "";
var imageIconElement = $('<div/>',{class:"palette_icon"}).appendTo(iconContainer); faIconElement.addClass("palette_icon_fa fa fa-fw " + faLarge + iconPath.file);
imageIconElement.css("backgroundImage", "url("+iconUrl+")"); 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 { return {

View File

@ -1924,7 +1924,6 @@ RED.view = (function() {
.attr("xlink:href",iconUrl) .attr("xlink:href",iconUrl)
.attr("class","fa-lg") .attr("class","fa-lg")
.attr("x",15) .attr("x",15)
.attr("y",21)
.attr("stroke","none") .attr("stroke","none")
.attr("fill","#ffffff") .attr("fill","#ffffff")
.attr("text-anchor","middle") .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").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").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(".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) { thisNode.selectAll(".node_button").attr("opacity",function(d) {
return (activeSubflow||!isButtonEnabled(d))?0.4:1 return (activeSubflow||!isButtonEnabled(d))?0.4:1
@ -2764,7 +2764,7 @@ RED.view = (function() {
RED.workspaces.show(new_default_workspace.id); 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_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 // TODO: pick a more sensible root node
if (new_ms.length > 0) { if (new_ms.length > 0) {

View File

@ -150,13 +150,14 @@ RED.workspaces = (function() {
$('<div class="form-row">'+ $('<div class="form-row">'+
'<label for="node-input-disabled-btn" data-i18n="editor:workspace.status"></label>'+ '<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;"/>'+ '<input type="checkbox" id="node-input-disabled" style="display: none;"/>'+
'</div>').appendTo(dialogForm); '</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>'+ '<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); '</div>').appendTo(dialogForm);
tabflowEditor = RED.editor.createEditor({ tabflowEditor = RED.editor.createEditor({
id: 'node-input-info', id: 'node-input-info',
@ -164,7 +165,26 @@ RED.workspaces = (function() {
value: "" 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) { dialogForm.find('#node-input-disabled-btn').on("click",function(e) {
var i = $(this).find("i"); var i = $(this).find("i");
@ -269,9 +289,8 @@ RED.workspaces = (function() {
}, },
minimumActiveTabWidth: 150, minimumActiveTabWidth: 150,
scrollable: true, scrollable: true,
addButton: function() { addButton: "core:add-flow",
addWorkspace(); addButtonCaption: RED._("workspace.addFlow")
}
}); });
workspaceTabCount = 0; workspaceTabCount = 0;
} }
@ -319,9 +338,9 @@ RED.workspaces = (function() {
if (workspace_tabs.contains(ws.id)) { if (workspace_tabs.contains(ws.id)) {
workspace_tabs.removeTab(ws.id); workspace_tabs.removeTab(ws.id);
} }
} if (ws.id === activeWorkspace) {
if (ws.id === activeWorkspace) { activeWorkspace = 0;
activeWorkspace = 0; }
} }
} }

View File

@ -6,3 +6,24 @@
border-top-right-radius: 4px; border-top-right-radius: 4px;
border-bottom-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; 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-expanded {
& #palette { & #palette {
width: 380px; width: 380px;

View File

@ -103,3 +103,30 @@
.sidebar-shade { .sidebar-shade {
@include 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; right: 322px;
overflow: hidden; overflow: hidden;
@include component-border; @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 { .workspace-footer-button {
@include component-footer-button; @include component-footer-button;
} }

View File

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

View File

@ -83,7 +83,7 @@
<p>This node can be used to create a timeout within a flow. By default, when <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 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>. 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 <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 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 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> </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"> <div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label> <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 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. <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> 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> <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 <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 the <code>msg.headers</code> property. The first node will set this property with
@ -141,7 +148,8 @@
method:{value:"GET"}, method:{value:"GET"},
ret: {value:"txt"}, ret: {value:"txt"},
url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, 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: { credentials: {
user: {type:"text"}, user: {type:"text"},
@ -192,6 +200,24 @@
$("#node-input-usetls").on("click",function() { $("#node-input-usetls").on("click",function() {
updateTLSOptions(); 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() { $("#node-input-ret").change(function() {
if ($("#node-input-ret").val() === "obj") { if ($("#node-input-ret").val() === "obj") {
$("#tip-json").show(); $("#tip-json").show();
@ -204,6 +230,9 @@
if (!$("#node-input-usetls").is(':checked')) { if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_"); $("#node-input-tls").val("_ADD_");
} }
if (!$("#node-input-useProxy").is(":checked")) {
$("#node-input-proxy").val("_ADD_");
}
} }
}); });
</script> </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(","); }
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) { this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime(); var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"}); 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.encoding = null; // Force NodeJs to return a Buffer (instead of a string)
opts.maxRedirects = 21; opts.maxRedirects = 21;
opts.jar = request.jar(); opts.jar = request.jar();
opts.proxy = null;
var ctSet = "Content-Type"; // set default camel case var ctSet = "Content-Type"; // set default camel case
var clSet = "Content-Length"; var clSet = "Content-Length";
if (msg.headers) { if (msg.headers) {
@ -206,6 +214,15 @@ module.exports = function(RED) {
opts.proxy = null; 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) { if (tlsNode) {
tlsNode.addTLSOptions(opts); tlsNode.addTLSOptions(opts);
} else { } else {

View File

@ -28,9 +28,11 @@ module.exports = function(RED) {
this.createDir = n.createDir || false; this.createDir = n.createDir || false;
var node = this; var node = this;
node.wstream = null; 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 || ""; var filename = node.filename || msg.filename || "";
if ((!node.filename) && (!node.tout)) { if ((!node.filename) && (!node.tout)) {
node.tout = setTimeout(function() { node.tout = setTimeout(function() {
@ -41,6 +43,7 @@ module.exports = function(RED) {
} }
if (filename === "") { if (filename === "") {
node.warn(RED._("file.errors.nofilename")); node.warn(RED._("file.errors.nofilename"));
done();
} else if (node.overwriteFile === "delete") { } else if (node.overwriteFile === "delete") {
fs.unlink(filename, function (err) { fs.unlink(filename, function (err) {
if (err) { if (err) {
@ -51,6 +54,7 @@ module.exports = function(RED) {
} }
node.send(msg); node.send(msg);
} }
done();
}); });
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) { } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename); var dir = path.dirname(filename);
@ -59,6 +63,7 @@ module.exports = function(RED) {
fs.ensureDirSync(dir); fs.ensureDirSync(dir);
} catch(err) { } catch(err) {
node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
done();
return; return;
} }
} }
@ -70,85 +75,142 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); } if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); } if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; } if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
node.data.push({msg:msg,data:Buffer.from(data)}); if (node.overwriteFile === "true") {
var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
while (node.data.length > 0) { node.wstream = wstream;
if (node.overwriteFile === "true") { wstream.on("error", function(err) {
(function(packet) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true }); done();
node.wstream.on("error", function(err) { });
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); wstream.on("open", function() {
}); wstream.end(data, function() {
node.wstream.on("open", function() { node.send(msg);
node.wstream.end(packet.data, function() { done();
node.send(packet.msg); });
}); })
}) return;
})(node.data.shift()); }
} else {
else { // Append mode
// Append mode var recreateStream = !node.wstream || !node.filename;
var recreateStream = !node.wstream || !node.filename; if (node.wstream && node.wstreamIno) {
if (node.wstream && node.wstreamIno) { // There is already a stream open and we have the inode
// There is already a stream open and we have the inode // of the file. Check the file hasn't been deleted
// of the file. Check the file hasn't been deleted // or deleted and recreated.
// or deleted and recreated. try {
try { var stat = fs.statSync(filename);
var stat = fs.statSync(filename); // File exists - check the inode matches
// File exists - check the inode matches if (stat.ino !== node.wstreamIno) {
if (stat.ino !== node.wstreamIno) { // The file has been recreated. Close the current
// The file has been recreated. Close the current // stream and recreate it
// stream and recreate it
recreateStream = true;
node.wstream.end();
delete node.wstream;
delete node.wstreamIno;
}
} catch(err) {
// File does not exist
recreateStream = true; recreateStream = true;
node.wstream.end(); node.wstream.end();
delete node.wstream; delete node.wstream;
delete node.wstreamIno; delete node.wstreamIno;
} }
} } catch(err) {
if (recreateStream) { // File does not exist
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true }); recreateStream = true;
node.wstream.on("open", function(fd) { node.wstream.end();
try {
var stat = fs.statSync(filename);
node.wstreamIno = stat.ino;
} catch(err) {
}
});
node.wstream.on("error", function(err) {
node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg);
});
}
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);
});
} else {
// Dynamic filename - write and close the stream
var packet = node.data.shift()
node.wstream.end(packet.data, function() {
node.send(packet.msg);
});
delete node.wstream; delete node.wstream;
delete node.wstreamIno; delete node.wstreamIno;
} }
} }
if (recreateStream) {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true });
node.wstream.on("open", function(fd) {
try {
var stat = fs.statSync(filename);
node.wstreamIno = stat.ino;
} catch(err) {
}
});
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
node.wstream.write(data, function() {
node.send(msg);
done();
});
} else {
// Dynamic filename - write and close the stream
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("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;
}
}); });
this.on('close', function() {
function closeNode() {
if (node.wstream) { node.wstream.end(); } if (node.wstream) { node.wstream.end(); }
if (node.tout) { clearTimeout(node.tout); } if (node.tout) { clearTimeout(node.tout); }
node.status({}); 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); RED.nodes.registerType("file",FileNode);

View File

@ -311,7 +311,7 @@
"title": "Title", "title": "Title",
"body": "Body" "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": { "unknown": {
"label": { "label": {
@ -382,6 +382,10 @@
"basicauth": "Use basic authentication", "basicauth": "Use basic authentication",
"use-tls": "Enable secure (SSL/TLS) connection", "use-tls": "Enable secure (SSL/TLS) connection",
"tls-config":"TLS Configuration", "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", "utf8": "a UTF-8 string",
"binary": "a binary buffer", "binary": "a binary buffer",
"json": "a parsed JSON object", "json": "a parsed JSON object",

View File

@ -15,7 +15,7 @@
--> -->
<script type="text/x-red" data-help-name="trigger"> <script type="text/x-red" data-help-name="trigger">
<p>メッセージ受信すると、別のメッセージの送信を行います。延長もしくは初期化が指定されていない場合には、2つ目のメッセージを送信することもできます。</p> <p>メッセージ受信すると、別のメッセージの送信を行います。延長もしくは初期化が指定されていない場合には、2つ目のメッセージを送信することもできます。</p>
<h3>入力</h3> <h3>入力</h3>
<dl class="message-properties"> <dl class="message-properties">
@ -24,7 +24,7 @@
</dl> </dl>
<h3>詳細</h3> <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>」とし、メッセージを受け取った時に遅延を延長することを選択した場合、triggerードは監視タイマとして動作します。すなわち、指定間隔内にメッセージを受信しない場合にメッセージを送信します。</p>
<p>ペイロードに<i>文字列</i>を指定する場合、mustache形式のテンプレートが利用できます。</p> <p>ペイロードに<i>文字列</i>を指定する場合、mustache形式のテンプレートが利用できます。</p>
<p><code>reset</code>プロパティを持つメッセージを受信した場合、もしくは、<code>payload</code>が設定した値にマッチする場合、仕掛かり中の待機や繰り返しをクリアしメッセージの送信は行いません。</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> <p><code>statusCode</code><code>headers</code>はノードの設定で指定することも可能です。ノードの設定でプロパティを指定した場合には、対応するメッセージプロパティは使用しません。</p>
<h4>クッキーの処理</h4> <h4>クッキーの処理</h4>
<p><code>cookies</code>はキー/値の組からなるオブジェクトとします。値にはデフォルトオプションを使ってクッキーの値として設定する文字列、もしくは、オプションを含むオブジェクトを指定できます。<p> <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> <pre>
msg.cookies = { msg.cookies = {
name: 'nick', name: 'nick',

View File

@ -49,7 +49,7 @@
</dl> </dl>
<h3>詳細</h3> <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>ードの設定で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> <h4>複数のHTTPリクエストードの利用</h4>
<p>同一フローで本ノードを複数利用するためには、<code>msg.headers</code>プロパティの扱いに注意しなくてはなりません。例えば、最初のノードがレスポンスヘッダにこのプロパティを設定し、次のノードがこのプロパティをリクエストヘッダに利用するというのは一般的には期待する動作ではありません。<code>msg.headers</code>プロパティをード間で変更しないままとすると、2つ目のードで無視されることになります。カスタムヘッダを設定するためには、<code>msg.headers</code>をまず削除もしくは空のオブジェクト<code>{}</code>にリセットします。 <p>同一フローで本ノードを複数利用するためには、<code>msg.headers</code>プロパティの扱いに注意しなくてはなりません。例えば、最初のノードがレスポンスヘッダにこのプロパティを設定し、次のノードがこのプロパティをリクエストヘッダに利用するというのは一般的には期待する動作ではありません。<code>msg.headers</code>プロパティをード間で変更しないままとすると、2つ目のードで無視されることになります。カスタムヘッダを設定するためには、<code>msg.headers</code>をまず削除もしくは空のオブジェクト<code>{}</code>にリセットします。
<h4>クッキーの扱い</h4> <h4>クッキーの扱い</h4>

View File

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

View File

@ -311,13 +311,13 @@
"title": "タイトル", "title": "タイトル",
"body": "本文" "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": { "unknown": {
"label": { "label": {
"unknown": "unknown" "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": { "mqtt": {
"label": { "label": {
@ -382,6 +382,10 @@
"basicauth": "ベーシック認証を使用", "basicauth": "ベーシック認証を使用",
"use-tls": "SSL/TLS接続を有効化", "use-tls": "SSL/TLS接続を有効化",
"tls-config": "TLS設定", "tls-config": "TLS設定",
"use-proxy": "プロキシを使用",
"proxy-config": "プロキシ設定",
"use-proxyauth": "プロキシ認証を使用",
"noproxy-hosts": "例外ホスト",
"utf8": "文字列", "utf8": "文字列",
"binary": "バイナリバッファ", "binary": "バイナリバッファ",
"json": "JSON", "json": "JSON",

View File

@ -301,7 +301,7 @@
"title": "标题", "title": "标题",
"body": "主体" "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": { "unknown": {
"label": { "label": {

View File

@ -21,6 +21,7 @@ var fs = require("fs");
var registry = require("./registry"); var registry = require("./registry");
var library = require("./library"); var library = require("./library");
var log; var log;
var exec;
var events; var events;
@ -36,6 +37,7 @@ function init(runtime) {
events = runtime.events; events = runtime.events;
settings = runtime.settings; settings = runtime.settings;
log = runtime.log; log = runtime.log;
exec = runtime.exec;
} }
var activePromise = Promise.resolve(); var activePromise = Promise.resolve();
@ -104,50 +106,40 @@ function installModule(module,version) {
var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var args = ['install','--save','--save-prefix="~"','--production',installName]; var args = ['install','--save','--save-prefix="~"','--production',installName];
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
var child = child_process.spawn(npmCommand,args,{ exec.run(npmCommand,args,{
cwd: installDir, cwd: installDir
shell: true }, true).then(result => {
}); if (!isUpgrade) {
var output = ""; log.info(log._("server.install.installed",{name:module}));
child.stdout.on('data', (data) => { resolve(require("./index").addModule(module).then(reportAddedModules));
output += data;
});
child.stderr.on('data', (data) => {
output += data;
});
child.on('close', (code) => {
if (code !== 0) {
var e;
var lookFor404 = new RegExp(" 404 .*"+module,"m");
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
if (lookFor404.test(output)) {
log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
} else { } else {
if (!isUpgrade) { log.info(log._("server.install.upgraded",{name:module, version:version}));
log.info(log._("server.install.installed",{name:module})); events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
resolve(require("./index").addModule(module).then(reportAddedModules)); resolve(require("./registry").setModulePendingUpdated(module,version));
} 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");
if (lookFor404.test(output)) {
log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
})
}); });
}).catch(err => { }).catch(err => {
// In case of error, reset activePromise to be resolvable // In case of error, reset activePromise to be resolvable
@ -208,25 +200,21 @@ function uninstallModule(module) {
var args = ['remove','--save',module]; var args = ['remove','--save',module];
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
var child = child_process.execFile(npmCommand,args, exec.run(npmCommand,args,{
{ cwd: installDir,
cwd: installDir },true).then(result => {
}, log.info(log._("server.install.uninstalled",{name:module}));
function(err, stdin, stdout) { reportRemovedModules(list);
if (err) { library.removeExamplesDir(module);
log.warn(log._("server.install.uninstall-failed-long",{name:module})); resolve(list);
log.warn("------------------------------------------"); }).catch(result => {
log.warn(err.toString()); var output = result.stderr;
log.warn("------------------------------------------"); log.warn(log._("server.install.uninstall-failed-long",{name:module}));
reject(new Error(log._("server.install.uninstall-failed",{name:module}))); log.warn("------------------------------------------");
} else { log.warn(output.toString());
log.info(log._("server.install.uninstalled",{name:module})); log.warn("------------------------------------------");
reportRemovedModules(list); reject(new Error(log._("server.install.uninstall-failed",{name:module})));
library.removeExamplesDir(module); });
resolve(list);
}
}
);
}); });
}).catch(err => { }).catch(err => {
// In case of error, reset activePromise to be resolvable // 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)); runtime.log.trace("runtime event: "+JSON.stringify(event));
publish("notification/"+event.id,event.payload||{},event.retain); 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) { function publish(topic,data,retain) {
if (retain) { if (retain) {
@ -64,6 +79,8 @@ var api = module.exports = {
runtime.events.on("runtime-event",handleRuntimeEvent); runtime.events.on("runtime-event",handleRuntimeEvent);
runtime.events.removeListener("comms",handleCommsEvent); runtime.events.removeListener("comms",handleCommsEvent);
runtime.events.on("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 library = require("./library");
var events = require("./events"); var events = require("./events");
var settings = require("./settings"); var settings = require("./settings");
var exec = require("./exec");
var express = require("express"); var express = require("express");
var path = require('path'); var path = require('path');
@ -71,6 +72,7 @@ function init(userSettings,_redUtil,_adminApi) {
redNodes.init(runtime); redNodes.init(runtime);
library.init(runtime); library.init(runtime);
externalAPI.init(runtime); externalAPI.init(runtime);
exec.init(runtime);
} }
var version; var version;
@ -248,6 +250,7 @@ var runtime = {
events: events, events: events,
nodes: redNodes, nodes: redNodes,
library: library, library: library,
exec: exec,
util: require("@node-red/util").util, util: require("@node-red/util").util,
get adminApi() { return adminApi }, get adminApi() { return adminApi },
get nodeApp() { return nodeApp }, get nodeApp() { return nodeApp },

View File

@ -104,7 +104,7 @@ function load() {
try { try {
plugin = require("./"+plugins[pluginName].module); plugin = require("./"+plugins[pluginName].module);
} catch(err) { } 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 { } else {
// Assume `module` is an already-required module we can use // 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})); log.info(log._("context.log-store-init", {name:pluginName, info:"module="+moduleInfo}));
} catch(err) { } 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 { } else {
// Plugin does not specify a 'module' // Plugin does not specify a 'module'

View File

@ -15,8 +15,9 @@
**/ **/
var when = require('when'); 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 authResponseServer = require('./authServer').ResponseServer;
var sshResponseServer = require('./authServer').ResponseSSHServer; var sshResponseServer = require('./authServer').ResponseSSHServer;
var clone = require('clone'); var clone = require('clone');
@ -26,83 +27,70 @@ var gitCommand = "git";
var gitVersion; var gitVersion;
var log; var log;
function runGitCommand(args,cwd,env) { function runGitCommand(args,cwd,env,emit) {
log.trace(gitCommand + JSON.stringify(args)); log.trace(gitCommand + JSON.stringify(args));
return when.promise(function(resolve,reject) { args.unshift("credential.helper=")
args.unshift("credential.helper=") args.unshift("-c");
args.unshift("-c"); return exec.run(gitCommand, args, {cwd:cwd, env:env}, emit).then(result => {
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env}); return result.stdout;
var stdout = ""; }).catch(result => {
var stderr = ""; var err = new Error(stderr);
child.stdout.on('data', function(data) { var stdout = result.stdout;
stdout += data; var stderr = result.stderr;
}); err.stdout = stdout;
child.stderr.on('data', function(data) { err.stderr = stderr;
stderr += data; if (/Connection refused/i.test(stderr)) {
}); err.code = "git_connection_failed";
child.on('error', function(err) { } else if (/Connection timed out/i.test(stderr)) {
stderr = err.toString(); err.code = "git_connection_failed";
}) } else if (/fatal: could not read/i.test(stderr)) {
child.on('close', function(code) { // Username/Password
if (code !== 0) { err.code = "git_auth_failed";
var err = new Error(stderr); } else if(/HTTP Basic: Access denied/i.test(stderr)) {
err.stdout = stdout; err.code = "git_auth_failed";
err.stderr = stderr; } else if(/Permission denied \(publickey\)/i.test(stderr)) {
if (/Connection refused/i.test(stderr)) { err.code = "git_auth_failed";
err.code = "git_connection_failed"; } else if(/Host key verification failed/i.test(stderr)) {
} else if (/Connection timed out/i.test(stderr)) { // TODO: handle host key verification errors separately
err.code = "git_connection_failed"; err.code = "git_auth_failed";
} else if (/fatal: could not read/i.test(stderr)) { } else if (/commit your changes or stash/i.test(stderr)) {
// Username/Password err.code = "git_local_overwrite";
err.code = "git_auth_failed"; } else if (/CONFLICT/.test(err.stdout)) {
} else if(/HTTP Basic: Access denied/i.test(stderr)) { err.code = "git_pull_merge_conflict";
err.code = "git_auth_failed"; } else if (/not fully merged/i.test(stderr)) {
} else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_delete_branch_unmerged";
err.code = "git_auth_failed"; } else if (/remote .* already exists/i.test(stderr)) {
} else if(/Host key verification failed/i.test(stderr)) { err.code = "git_remote_already_exists";
// TODO: handle host key verification errors separately } else if (/does not appear to be a git repository/i.test(stderr)) {
err.code = "git_auth_failed"; err.code = "git_not_a_repository";
} else if (/commit your changes or stash/i.test(stderr)) { } else if (/Repository not found/i.test(stderr)) {
err.code = "git_local_overwrite"; err.code = "git_repository_not_found";
} else if (/CONFLICT/.test(err.stdout)) { } else if (/repository '.*' does not exist/i.test(stderr)) {
err.code = "git_pull_merge_conflict"; err.code = "git_repository_not_found";
} else if (/not fully merged/i.test(stderr)) { } else if (/refusing to merge unrelated histories/i.test(stderr)) {
err.code = "git_delete_branch_unmerged"; err.code = "git_pull_unrelated_history"
} else if (/remote .* already exists/i.test(stderr)) { } else if (/Please tell me who you are/i.test(stderr)) {
err.code = "git_remote_already_exists"; err.code = "git_missing_user";
} else if (/does not appear to be a git repository/i.test(stderr)) { } else if (/name consists only of disallowed characters/i.test(stderr)) {
err.code = "git_not_a_repository"; err.code = "git_missing_user";
} else if (/Repository not found/i.test(stderr)) { }
err.code = "git_repository_not_found"; throw err;
} else if (/repository '.*' does not exist/i.test(stderr)) { })
err.code = "git_repository_not_found";
} else if (/refusing to merge unrelated histories/i.test(stderr)) {
err.code = "git_pull_unrelated_history"
} else if (/Please tell me who you are/i.test(stderr)) {
err.code = "git_missing_user";
} else if (/name consists only of disallowed characters/i.test(stderr)) {
err.code = "git_missing_user";
}
return reject(err);
}
resolve(stdout);
});
});
} }
function runGitCommandWithAuth(args,cwd,auth) { function runGitCommandWithAuth(args,cwd,auth,emit) {
return authResponseServer(auth).then(function(rs) { return authResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env); var commandEnv = clone(process.env);
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh"); commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath; commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path; commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js"); 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(); rs.close();
}); });
}) })
} }
function runGitCommandWithSSHCommand(args,cwd,auth) { function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
return sshResponseServer(auth).then(function(rs) { return sshResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env); var commandEnv = clone(process.env);
commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh"); 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 // GIT_SSH_COMMAND - added in git 2.3.0
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null"; commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
// console.log('commandEnv:', commandEnv); // console.log('commandEnv:', commandEnv);
return runGitCommand(args,cwd,commandEnv).finally(function() { return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
rs.close(); rs.close();
}); });
}) })
@ -448,13 +436,13 @@ module.exports = {
var promise; var promise;
if (auth) { if (auth) {
if ( auth.key_path ) { if ( auth.key_path ) {
promise = runGitCommandWithSSHCommand(args,cwd,auth); promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
} }
else { else {
promise = runGitCommandWithAuth(args,cwd,auth); promise = runGitCommandWithAuth(args,cwd,auth,true);
} }
} else { } else {
promise = runGitCommand(args,cwd) promise = runGitCommand(args,cwd,undefined,true)
} }
return promise; return promise;
// .catch(function(err) { // .catch(function(err) {
@ -485,13 +473,13 @@ module.exports = {
var promise; var promise;
if (auth) { if (auth) {
if ( auth.key_path ) { if ( auth.key_path ) {
promise = runGitCommandWithSSHCommand(args,cwd,auth); promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
} }
else { else {
promise = runGitCommandWithAuth(args,cwd,auth); promise = runGitCommandWithAuth(args,cwd,auth,true);
} }
} else { } else {
promise = runGitCommand(args,cwd) promise = runGitCommand(args,cwd,undefined,true)
} }
return promise.catch(function(err) { return promise.catch(function(err) {
if (err.code === 'git_error') { if (err.code === 'git_error') {
@ -517,13 +505,13 @@ module.exports = {
args.push("."); args.push(".");
if (auth) { if (auth) {
if ( auth.key_path ) { if ( auth.key_path ) {
return runGitCommandWithSSHCommand(args,cwd,auth); return runGitCommandWithSSHCommand(args,cwd,auth,true);
} }
else { else {
return runGitCommandWithAuth(args,cwd,auth); return runGitCommandWithAuth(args,cwd,auth,true);
} }
} else { } else {
return runGitCommand(args,cwd); return runGitCommand(args,cwd,undefined,true);
} }
}, },
getStatus: getStatus, getStatus: getStatus,

View File

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

View File

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

View File

@ -148,7 +148,7 @@ module.exports = {
// The following property can be used to cause insecure HTTP connections to // The following property can be used to cause insecure HTTP connections to
// be redirected to HTTPS. // be redirected to HTTPS.
//requireHttps: true //requireHttps: true,
// The following property can be used to disable the editor. The admin API // 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 // is not affected by this option. To disable both the editor and the admin
@ -181,6 +181,11 @@ module.exports = {
// next(); // 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. // The following property can be used to verify websocket connection attempts.
// This allows, for example, the HTTP request headers to be checked to ensure // This allows, for example, the HTTP request headers to be checked to ensure
// they include valid authentication information. // they include valid authentication information.
@ -256,5 +261,5 @@ module.exports = {
// To enable the Projects feature, set this value to true // To enable the Projects feature, set this value to true
enabled: false enabled: false
} }
}, }
} }

View File

@ -21,7 +21,7 @@ var fs = require('fs-extra');
var path = require('path'); var path = require('path');
var app = express(); 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"); var utilPage = require("./pageobjects/util/util_page");

View File

@ -16,7 +16,7 @@
var when = require("when"); 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 palette = require("./palette_page");
var nodeFactory = require("../nodes/nodefactory_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'); browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]/button');
if (complete !== 'true') { if (complete !== 'true') {
// Select the "msg" type. // 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. // Input the path in msg.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input'); browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input');
browser.keys(['Control', 'a', 'Control']); browser.keys(['Control', 'a', 'Control']);

View File

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

View File

@ -315,8 +315,9 @@ describe('cookbook', function() {
debugTab.open(); debugTab.open();
debugTab.clearMessage(); debugTab.clearMessage();
injectNode.clickLeftButton(); injectNode.clickLeftButton();
var message = debugTab.getMessage(); var messages = debugTab.getMessage();
message[1].indexOf('application/json').should.not.eql(-1); var contents = messages.join([separator = ""]);
contents.indexOf('application/json').should.not.eql(-1);
}); });
it('serve a local file', function () { 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() { 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 helper = require("node-red-node-test-helper");
var httpRequestNode = require("nr-test-utils").require("@node-red/nodes/core/io/21-httprequest.js"); 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 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 hashSum = require("hash-sum");
var httpProxy = require('http-proxy'); var httpProxy = require('http-proxy');
var cookieParser = require('cookie-parser'); var cookieParser = require('cookie-parser');
@ -1194,6 +1195,80 @@ describe('HTTP Request Node', function() {
n1.receive({payload:"foo"}); 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() { describe('authentication', function() {
@ -1264,6 +1339,63 @@ describe('HTTP Request Node', function() {
n1.receive({payload:"foo"}); 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() { describe('redirect-cookie', function() {

View File

@ -46,14 +46,14 @@ describe('file Nodes', function() {
it('should be loaded', function(done) { it('should be loaded', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":true}]; var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":true}];
helper.load(fileNode, flow, function() { helper.load(fileNode, flow, function() {
try { try {
var fileNode1 = helper.getNode("fileNode1"); var fileNode1 = helper.getNode("fileNode1");
fileNode1.should.have.property('name', 'fileNode'); fileNode1.should.have.property('name', 'fileNode');
done(); done();
} }
catch (e) { catch (e) {
done(e); done(e);
} }
}); });
}); });
@ -64,16 +64,16 @@ describe('file Nodes', function() {
var n1 = helper.getNode("fileNode1"); var n1 = helper.getNode("fileNode1");
var n2 = helper.getNode("helperNode1"); var n2 = helper.getNode("helperNode1");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
var f = fs.readFileSync(fileToTest); var f = fs.readFileSync(fileToTest);
f.should.have.length(4); f.should.have.length(4);
fs.unlinkSync(fileToTest); fs.unlinkSync(fileToTest);
msg.should.have.property("payload", "test"); msg.should.have.property("payload", "test");
done(); done();
} }
catch (e) { catch (e) {
done(e); done(e);
} }
}); });
n1.receive({payload:"test"}); n1.receive({payload:"test"});
}); });
@ -93,26 +93,26 @@ describe('file Nodes', function() {
var data = ["test2", true, 999, [2]]; var data = ["test2", true, 999, [2]];
n2.on("input", function (msg) { n2.on("input", function (msg) {
try { try {
msg.should.have.property("payload"); msg.should.have.property("payload");
data.should.containDeep([msg.payload]); data.should.containDeep([msg.payload]);
if (count === 3) { if (count === 3) {
var f = fs.readFileSync(fileToTest).toString(); var f = fs.readFileSync(fileToTest).toString();
if (os.type() !== "Windows_NT") { if (os.type() !== "Windows_NT") {
f.should.have.length(19); f.should.have.length(19);
f.should.equal("test2\ntrue\n999\n[2]\n"); f.should.equal("test2\ntrue\n999\n[2]\n");
} }
else { else {
f.should.have.length(23); f.should.have.length(23);
f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n"); f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n");
} }
done(); done();
} }
count++; count++;
} }
catch (e) { catch (e) {
done(e); done(e);
} }
}); });
n1.receive({payload:"test2"}); // string n1.receive({payload:"test2"}); // string
@ -142,37 +142,37 @@ describe('file Nodes', function() {
var count = 0; var count = 0;
n2.on("input", function (msg) { n2.on("input", function (msg) {
try { try {
msg.should.have.property("payload"); msg.should.have.property("payload");
data.should.containDeep([msg.payload]); data.should.containDeep([msg.payload]);
try { try {
if (count === 1) { if (count === 1) {
// Check they got appended as expected // Check they got appended as expected
var f = fs.readFileSync(fileToTest).toString(); var f = fs.readFileSync(fileToTest).toString();
f.should.equal("onetwo"); f.should.equal("onetwo");
// Delete the file // Delete the file
fs.unlinkSync(fileToTest); fs.unlinkSync(fileToTest);
setTimeout(function() { setTimeout(function() {
// Send two more messages to the file // Send two more messages to the file
n1.receive({payload:"three"}); n1.receive({payload:"three"});
n1.receive({payload:"four"}); n1.receive({payload:"four"});
}, wait); }, wait);
} }
if (count === 3) { if (count === 3) {
var f = fs.readFileSync(fileToTest).toString(); var f = fs.readFileSync(fileToTest).toString();
f.should.equal("threefour"); f.should.equal("threefour");
fs.unlinkSync(fileToTest); fs.unlinkSync(fileToTest);
done(); done();
} }
} catch(err) { } catch(err) {
done(err); done(err);
} }
count++; count++;
} }
catch (e) { catch (e) {
done(e); done(e);
} }
}); });
// Send two messages to the file // Send two messages to the file
@ -197,7 +197,7 @@ describe('file Nodes', function() {
n2.on("input", function (msg) { n2.on("input", function (msg) {
try { try {
msg.should.have.property("payload"); msg.should.have.property("payload");
data.should.containDeep([msg.payload]); data.should.containDeep([msg.payload]);
if (count == 1) { if (count == 1) {
// Check they got appended as expected // Check they got appended as expected
var f = fs.readFileSync(fileToTest).toString(); var f = fs.readFileSync(fileToTest).toString();
@ -256,25 +256,25 @@ describe('file Nodes', function() {
var n2 = helper.getNode("helperNode1"); var n2 = helper.getNode("helperNode1");
n2.on("input", function (msg) { n2.on("input", function (msg) {
try { try {
msg.should.have.property("payload", "fine"); msg.should.have.property("payload", "fine");
msg.should.have.property("filename", fileToTest); msg.should.have.property("filename", fileToTest);
var f = fs.readFileSync(fileToTest).toString(); var f = fs.readFileSync(fileToTest).toString();
if (os.type() !== "Windows_NT") { if (os.type() !== "Windows_NT") {
f.should.have.length(5); f.should.have.length(5);
f.should.equal("fine\n"); f.should.equal("fine\n");
} }
else { else {
f.should.have.length(6); f.should.have.length(6);
f.should.equal("fine\r\n"); f.should.equal("fine\r\n");
} }
done(); done();
} }
catch (e) { catch (e) {
done(e); done(e);
} }
}); });
n1.receive({payload:"fine", filename:fileToTest}); n1.receive({payload:"fine", filename:fileToTest});
}); });
@ -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();
});
});
}); });
@ -573,7 +660,7 @@ describe('file Nodes', function() {
it('should read in a file and output a buffer', function(done) { it('should read in a file and output a buffer', function(done) {
var flow = [{id:"fileInNode1", type:"file in", name:"fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]}, var flow = [{id:"fileInNode1", type:"file in", name:"fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(fileNode, flow, function() { helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileInNode1"); var n1 = helper.getNode("fileInNode1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -590,7 +677,7 @@ describe('file Nodes', function() {
it('should read in a file and output a utf8 string', function(done) { it('should read in a file and output a utf8 string', function(done) {
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"utf8", wires:[["n2"]]}, var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"utf8", wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(fileNode, flow, function() { helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileInNode1"); var n1 = helper.getNode("fileInNode1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -632,7 +719,7 @@ describe('file Nodes', function() {
it('should read in a file and output split lines with parts', function(done) { it('should read in a file and output split lines with parts', function(done) {
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]}, var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(fileNode, flow, function() { helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileInNode1"); var n1 = helper.getNode("fileInNode1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -670,7 +757,7 @@ describe('file Nodes', function() {
var line = data.join("\n"); var line = data.join("\n");
fs.writeFileSync(fileToTest, line); fs.writeFileSync(fileToTest, line);
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]}, var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(fileNode, flow, function() { helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileInNode1"); var n1 = helper.getNode("fileInNode1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -703,7 +790,7 @@ describe('file Nodes', function() {
it('should read in a file and output a buffer with parts', function(done) { it('should read in a file and output a buffer with parts', function(done) {
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"stream", wires:[["n2"]]}, var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"stream", wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(fileNode, flow, function() { helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileInNode1"); var n1 = helper.getNode("fileInNode1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");

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() { describe("#create",function() {

View File

@ -91,6 +91,30 @@ describe("api/editor/locales", function() {
done(); 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() { // 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"); var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme");
describe("api/editor/theme", function() { describe("api/editor/theme", function () {
beforeEach(function() { beforeEach(function () {
sinon.stub(fs,"statSync",function() { return true; }); sinon.stub(fs, "statSync", function () { return true; });
}); });
afterEach(function() { afterEach(function () {
theme.init({settings:{}}); theme.init({settings: {}});
fs.statSync.restore(); fs.statSync.restore();
}); });
it("applies the default theme", function() { it("applies the default theme", function () {
var result = theme.init({}); var result = theme.init({});
should.not.exist(result); should.not.exist(result);
var context = theme.context(); var context = theme.context();
context.should.have.a.property("page"); context.should.have.a.property("page");
context.page.should.have.a.property("title","Node-RED"); context.page.should.have.a.property("title", "Node-RED");
context.page.should.have.a.property("favicon","favicon.ico"); 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.should.have.a.property("header");
context.header.should.have.a.property("title","Node-RED"); 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("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()); should.not.exist(theme.settings());
}); });
it("picks up custom theme", function() { it("picks up custom theme", function () {
theme.init({ theme.init({
editorTheme: { editorTheme: {
page: { page: {
title: "Test Page Title", 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", css: "/absolute/path/to/custom/css/file.css",
scripts: "/absolute/path/to/script.js" scripts: "/absolute/path/to/script.js"
}, },
header: { header: {
title: "Test Header Title", title: "Test Header Title",
url: "http://nodered.org",
image: "/absolute/path/to/header/image" // or null to remove image image: "/absolute/path/to/header/image" // or null to remove image
}, },
deployButton: { deployButton: {
type:"simple", type: "simple",
label:"Save", label: "Save",
icon: "/absolute/path/to/deploy/button/image" // or null to remove image icon: "/absolute/path/to/deploy/button/image" // or null to remove image
}, },
@ -83,6 +89,16 @@ describe("api/editor/theme", function() {
login: { login: {
image: "/absolute/path/to/login/page/big/image" // a 256x256 image 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(); var context = theme.context();
context.should.have.a.property("page"); 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.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.should.have.a.property("css");
context.page.css.should.have.lengthOf(1); context.page.css.should.have.lengthOf(1);
context.page.css[0].should.eql('theme/css/file.css'); context.page.css[0].should.eql('theme/css/file.css');
context.page.should.have.a.property("scripts"); context.page.should.have.a.property("scripts");
context.page.scripts.should.have.lengthOf(1); context.page.scripts.should.have.lengthOf(1);
context.page.scripts[0].should.eql('theme/scripts/script.js'); 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(); var settings = theme.settings();
settings.should.have.a.property("deployButton"); 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.should.have.a.property("userMenu");
settings.userMenu.should.be.eql(false);
settings.should.have.a.property("menu"); 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 fs = require('fs');
var EventEmitter = require('events'); var EventEmitter = require('events');
var child_process = require('child_process');
var NR_TEST_UTILS = require("nr-test-utils"); var NR_TEST_UTILS = require("nr-test-utils");
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer"); var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
@ -42,16 +39,21 @@ describe('nodes/registry/installer', function() {
_: function() { return "abc"} _: function() { return "abc"}
} }
before(function() { beforeEach(function() {
installer.init({log:mockLog, settings:{}, events: new EventEmitter()}); 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() { afterEach(function() {
if (child_process.spawn.restore) {
child_process.spawn.restore();
}
if (child_process.execFile.restore) {
child_process.execFile.restore();
}
if (registry.addModule.restore) { if (registry.addModule.restore) {
registry.addModule.restore(); registry.addModule.restore();
} }
@ -76,39 +78,33 @@ describe('nodes/registry/installer', function() {
describe("installs module", function() { describe("installs module", function() {
it("rejects when npm returns a 404", function(done) { it("rejects when npm returns a 404", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt) { var res = {
var ee = new EventEmitter(); code: 1,
ee.stdout = new EventEmitter(); stdout:"",
ee.stderr = new EventEmitter(); stderr:" 404 this_wont_exist"
setTimeout(function() { }
ee.stderr.emit('data'," 404 this_wont_exist"); var p = Promise.reject(res);
ee.emit('close',1); p.catch((err)=>{});
},10) initInstaller(p)
return ee;
});
installer.installModule("this_wont_exist").catch(function(err) { installer.installModule("this_wont_exist").catch(function(err) {
err.should.have.property("code",404); err.should.have.property("code",404);
done(); done();
}); });
}); });
it("rejects when npm does not find specified version", function(done) { it("rejects when npm does not find specified version", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt) { var res = {
var ee = new EventEmitter(); code: 1,
ee.stdout = new EventEmitter(); stdout:"",
ee.stderr = new EventEmitter(); stderr:" version not found: this_wont_exist@0.1.2"
setTimeout(function() { }
ee.stderr.emit('data'," version not found: this_wont_exist@0.1.2"); var p = Promise.reject(res);
ee.emit('close',1); p.catch((err)=>{});
},10) initInstaller(p)
return ee;
});
sinon.stub(typeRegistry,"getModuleInfo", function() { sinon.stub(typeRegistry,"getModuleInfo", function() {
return { return {
version: "0.1.1" version: "0.1.1"
} }
}); });
installer.installModule("this_wont_exist","0.1.2").catch(function(err) { installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql(404); err.code.should.be.eql(404);
done(); done();
@ -126,17 +122,14 @@ describe('nodes/registry/installer', function() {
}); });
}); });
it("rejects with generic error", function(done) { it("rejects with generic error", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt,cb) { var res = {
var ee = new EventEmitter(); code: 1,
ee.stdout = new EventEmitter(); stdout:"",
ee.stderr = new EventEmitter(); stderr:" kaboom!"
setTimeout(function() { }
ee.stderr.emit('data'," kaboom!"); var p = Promise.reject(res);
ee.emit('close',1); p.catch((err)=>{});
},10) initInstaller(p)
return ee;
});
installer.installModule("this_wont_exist").then(function() { installer.installModule("this_wont_exist").then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
}).catch(function(err) { }).catch(function(err) {
@ -145,15 +138,16 @@ describe('nodes/registry/installer', function() {
}); });
it("succeeds when module is found", function(done) { it("succeeds when module is found", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}}; var nodeInfo = {nodes:{module:"foo",types:["a"]}};
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter(); var res = {
ee.stdout = new EventEmitter(); code: 0,
ee.stderr = new EventEmitter(); stdout:"",
setTimeout(function() { stderr:""
ee.emit('close',0); }
},10) var p = Promise.resolve(res);
return ee; p.catch((err)=>{});
}); initInstaller(p)
var addModule = sinon.stub(registry,"addModule",function(md) { var addModule = sinon.stub(registry,"addModule",function(md) {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
@ -191,15 +185,15 @@ describe('nodes/registry/installer', function() {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule")); 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(); var res = {
ee.stdout = new EventEmitter(); code: 0,
ee.stderr = new EventEmitter(); stdout:"",
setTimeout(function() { stderr:""
ee.emit('close',0); }
},10) var p = Promise.resolve(res);
return ee; p.catch((err)=>{});
}); initInstaller(p)
installer.installModule(resourcesDir).then(function(info) { installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo); info.should.eql(nodeInfo);
done(); done();
@ -226,9 +220,14 @@ describe('nodes/registry/installer', function() {
var removeModule = sinon.stub(registry,"removeModule",function(md) { var removeModule = sinon.stub(registry,"removeModule",function(md) {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { var res = {
cb(new Error("test_error"),"",""); code: 1,
}); stdout:"",
stderr:"error"
}
var p = Promise.reject(res);
p.catch((err)=>{});
initInstaller(p)
installer.uninstallModule("this_wont_exist").then(function() { installer.uninstallModule("this_wont_exist").then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
@ -244,9 +243,14 @@ describe('nodes/registry/installer', function() {
var getModuleInfo = sinon.stub(registry,"getModuleInfo",function(md) { var getModuleInfo = sinon.stub(registry,"getModuleInfo",function(md) {
return {nodes:[]}; return {nodes:[]};
}); });
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { var res = {
cb(null,"",""); code: 0,
}); stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
initInstaller(p)
sinon.stub(fs,"statSync", function(fn) { return {}; }); 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");