diff --git a/Gruntfile.js b/Gruntfile.js index 272290495..411b6305f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -156,6 +156,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tray.js", "packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js", "packages/node_modules/@node-red/editor-client/src/js/ui/library.js", diff --git a/package.json b/package.json index b5d15970d..570173117 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "istanbul": "0.4.5", "minami": "1.2.3", "mocha": "^5.2.0", + "mosca": "^2.8.3", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.0.6", diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index 138dd12f5..158d0f77a 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -39,8 +39,9 @@ server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange)) function init(_settings,storage) { settings = _settings; if (settings.adminAuth) { - Users.init(settings.adminAuth); - Tokens.init(settings.adminAuth,storage); + var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module); + Users.init(mergedAdminAuth); + Tokens.init(mergedAdminAuth,storage); } } @@ -79,23 +80,24 @@ function getToken(req,res,next) { function login(req,res) { var response = {}; if (settings.adminAuth) { - if (settings.adminAuth.type === "credentials") { + var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module); + if (mergedAdminAuth.type === "credentials") { response = { "type":"credentials", "prompts":[{id:"username",type:"text",label:"user.username"},{id:"password",type:"password",label:"user.password"}] } - } else if (settings.adminAuth.type === "strategy") { + } else if (mergedAdminAuth.type === "strategy") { var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot; response = { "type":"strategy", - "prompts":[{type:"button",label:settings.adminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] + "prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] } - if (settings.adminAuth.strategy.icon) { - response.prompts[0].icon = settings.adminAuth.strategy.icon; + if (mergedAdminAuth.strategy.icon) { + response.prompts[0].icon = mergedAdminAuth.strategy.icon; } - if (settings.adminAuth.strategy.image) { - response.prompts[0].image = theme.serveFile('/login/',settings.adminAuth.strategy.image); + if (mergedAdminAuth.strategy.image) { + response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image); } } if (theme.context().login && theme.context().login.image) { diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/tokens.js b/packages/node_modules/@node-red/editor-api/lib/auth/tokens.js index 620cd6a12..daa058787 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/tokens.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/tokens.js @@ -32,6 +32,8 @@ var sessions = {}; var loadedSessions = null; +var apiAccessTokens; + function expireSessions() { var now = Date.now(); var modified = false; @@ -67,16 +69,33 @@ module.exports = { // At this point, storage will not have been initialised, so defer loading // the sessions until there's a request for them. loadedSessions = null; + + apiAccessTokens = {}; + if ( Array.isArray(adminAuthSettings.tokens) ) { + apiAccessTokens = adminAuthSettings.tokens.reduce(function(prev, current) { + prev[current.token] = { + user: current.user, + scope: current.scope + }; + return prev; + }, {}); + } return Promise.resolve(); }, get: function(token) { return loadSessions().then(function() { - if (sessions[token]) { - if (sessions[token].expires < Date.now()) { - return expireSessions().then(function() { return null }); + var info = apiAccessTokens[token] || null; + + if (info) { + return Promise.resolve(info); + } else { + if (sessions[token]) { + if (sessions[token].expires < Date.now()) { + return expireSessions().then(function() { return null }); + } } + return Promise.resolve(sessions[token]); } - return Promise.resolve(sessions[token]); }); }, create: function(user,client,scope) { diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index 596fba67e..dbf4c18f2 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -64,6 +64,11 @@ module.exports = { } }); } + if (settings.httpServerOptions) { + for (var eOption in settings.httpServerOptions) { + editorApp.set(eOption, settings.httpServerOptions[eOption]); + } + } editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor); editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,apiUtil.errorHandler); diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js index ecf21d1ff..5e10647e7 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js @@ -21,6 +21,18 @@ var i18n = require("@node-red/util").i18n; // TODO: separate module var runtimeAPI; +function loadResource(lang, namespace) { + var catalog = i18n.i.getResourceBundle(lang, namespace); + if (!catalog) { + var parts = lang.split("-"); + if (parts.length == 2) { + var new_lang = parts[0]; + return i18n.i.getResourceBundle(new_lang, namespace); + } + } + return catalog; +} + module.exports = { init: function(_runtimeAPI) { runtimeAPI = _runtimeAPI; @@ -33,7 +45,7 @@ module.exports = { var prevLang = i18n.i.language; // Trigger a load from disk of the language if it is not the default i18n.i.changeLanguage(lang, function(){ - var catalog = i18n.i.getResourceBundle(lang, namespace); + var catalog = loadResource(lang, namespace); res.json(catalog||{}); }); i18n.i.changeLanguage(prevLang); diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/projects.js b/packages/node_modules/@node-red/editor-api/lib/editor/projects.js index 468580459..ff7fc5e85 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/projects.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/projects.js @@ -20,6 +20,58 @@ var apiUtils = require("../util"); var runtimeAPI; var needsPermission = require("../auth").needsPermission; +function listProjects(req,res) { + var opts = { + user: req.user + } + runtimeAPI.projects.listProjects(opts).then(function(result) { + res.json(result); + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }); +} +function getProject(req,res) { + var opts = { + user: req.user, + id: req.params.id + } + runtimeAPI.projects.getProject(opts).then(function(data) { + if (data) { + res.json(data); + } else { + res.status(404).end(); + } + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) +} +function getProjectStatus(req,res) { + var opts = { + user: req.user, + id: req.params.id, + remote: req.query.remote + } + runtimeAPI.projects.getStatus(opts).then(function(data){ + if (data) { + res.json(data); + } else { + res.status(404).end(); + } + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) +} +function getProjectRemotes(req,res) { + var opts = { + user: req.user, + id: req.params.id + } + runtimeAPI.projects.getRemotes(opts).then(function(data) { + res.json(data); + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) +} module.exports = { init: function(_runtimeAPI) { runtimeAPI = _runtimeAPI; @@ -40,16 +92,7 @@ module.exports = { // Projects // List all projects - app.get("/", needsPermission("projects.read"), function(req,res) { - var opts = { - user: req.user - } - runtimeAPI.projects.listProjects(opts).then(function(result) { - res.json(result); - }).catch(function(err) { - apiUtils.rejectHandler(req,res,err); - }); - }); + app.get("/", needsPermission("projects.read"),listProjects); // Create project app.post("/", needsPermission("projects.write"), function(req,res) { @@ -74,13 +117,13 @@ module.exports = { if (req.body.active) { runtimeAPI.projects.setActiveProject(opts).then(function() { - res.redirect(303,req.baseUrl + '/'); + listProjects(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) } else if (req.body.initialise) { runtimeAPI.projects.initialiseProject(opts).then(function() { - res.redirect(303,req.baseUrl + '/'+ req.params.id); + getProject(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -91,7 +134,7 @@ module.exports = { req.body.hasOwnProperty('files') || req.body.hasOwnProperty('git')) { runtimeAPI.projects.updateProject(opts).then(function() { - res.redirect(303,req.baseUrl + '/'+ req.params.id); + getProject(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -101,21 +144,7 @@ module.exports = { }); // Get project metadata - app.get("/:id", needsPermission("projects.read"), function(req,res) { - var opts = { - user: req.user, - id: req.params.id - } - runtimeAPI.projects.getProject(opts).then(function(data) { - if (data) { - res.json(data); - } else { - res.status(404).end(); - } - }).catch(function(err) { - apiUtils.rejectHandler(req,res,err); - }) - }); + app.get("/:id", needsPermission("projects.read"), getProject); // Delete project app.delete("/:id", needsPermission("projects.write"), function(req,res) { @@ -132,22 +161,7 @@ module.exports = { // Get project status - files, commit counts, branch info - app.get("/:id/status", needsPermission("projects.read"), function(req,res) { - var opts = { - user: req.user, - id: req.params.id, - remote: req.query.remote - } - runtimeAPI.projects.getStatus(opts).then(function(data){ - if (data) { - res.json(data); - } else { - res.status(404).end(); - } - }).catch(function(err) { - apiUtils.rejectHandler(req,res,err); - }) - }); + app.get("/:id/status", needsPermission("projects.read"), getProjectStatus); // Project file listing @@ -203,7 +217,7 @@ module.exports = { path: req.params[0] } runtimeAPI.projects.stageFile(opts).then(function() { - res.redirect(303,req.baseUrl+"/"+opts.id+"/status"); + getProjectStatus(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -217,7 +231,7 @@ module.exports = { path: req.body.files } runtimeAPI.projects.stageFile(opts).then(function() { - res.redirect(303,req.baseUrl+"/"+opts.id+"/status"); + getProjectStatus(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -231,7 +245,7 @@ module.exports = { message: req.body.message } runtimeAPI.projects.commit(opts).then(function() { - res.redirect(303,req.baseUrl+"/"+opts.id+"/status"); + getProjectStatus(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -245,7 +259,7 @@ module.exports = { path: req.params[0] } runtimeAPI.projects.unstageFile(opts).then(function() { - res.redirect(303,req.baseUrl+"/"+opts.id+"/status"); + getProjectStatus(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -258,7 +272,7 @@ module.exports = { id: req.params.id } runtimeAPI.projects.unstageFile(opts).then(function() { - res.redirect(303,req.baseUrl+"/"+opts.id+"/status"); + getProjectStatus(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -442,17 +456,7 @@ module.exports = { }); // Get a list of remotes - app.get("/:id/remotes", needsPermission("projects.read"), function(req, res) { - var opts = { - user: req.user, - id: req.params.id - } - runtimeAPI.projects.getRemotes(opts).then(function(data) { - res.json(data); - }).catch(function(err) { - apiUtils.rejectHandler(req,res,err); - }) - }); + app.get("/:id/remotes", needsPermission("projects.read"), getProjectRemotes); // Add a remote app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) { @@ -466,7 +470,7 @@ module.exports = { return; } runtimeAPI.projects.addRemote(opts).then(function(data) { - res.redirect(303,req.baseUrl+"/"+opts.id+"/remotes"); + getProjectRemotes(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -480,7 +484,7 @@ module.exports = { remote: req.params.remoteName } runtimeAPI.projects.removeRemote(opts).then(function(data) { - res.redirect(303,req.baseUrl+"/"+opts.id+"/remotes"); + getProjectRemotes(req,res); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 34ba30370..c604ac463 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -23,6 +23,7 @@ "confirmDelete": "Confirm delete", "delete": "Are you sure you want to delete '__label__'?", "dropFlowHere": "Drop the flow here", + "addFlow": "Add Flow", "status": "Status", "enabled": "Enabled", "disabled":"Disabled", @@ -46,6 +47,9 @@ "sidebar": { "show": "Show sidebar" }, + "palette": { + "show": "Show palette" + }, "settings": "Settings", "userSettings": "User Settings", "nodes": "Nodes", @@ -212,6 +216,10 @@ "plusNMore": "+ __count__ more" } }, + "eventLog": { + "title": "Event log", + "view": "View log" + }, "diff": { "unresolvedCount": "__count__ unresolved conflict", "unresolvedCount_plural": "__count__ unresolved conflicts", @@ -444,6 +452,7 @@ "label": "info", "node": "Node", "type": "Type", + "module": "Module", "id": "ID", "status": "Status", "enabled": "Enabled", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 3a7570784..eb1ae260e 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -78,6 +78,12 @@ "projects-settings": "設定" } }, + "actions": { + "toggle-navigator": "ナビゲータの表示/非表示を切替", + "zoom-out": "縮小", + "zoom-reset": "拡大/縮小を初期化", + "zoom-in": "拡大" + }, "user": { "loggedInAs": "__name__ としてログインしました", "username": "ユーザ名", @@ -334,6 +340,10 @@ "analysis": "分析", "advanced": "その他" }, + "actions": { + "collapse-all": "全カテゴリを折畳む", + "expand-all": "全カテゴリを展開" + }, "event": { "nodeAdded": "ノードをパレットへ追加しました:", "nodeAdded_plural": "ノードをパレットへ追加しました", @@ -486,7 +496,9 @@ "editDescription": "プロジェクトの詳細を編集", "editDependencies": "プロジェクトの依存関係を編集", "editReadme": "README.mdを編集", + "showProjectSettings": "プロジェクト設定を表示", "projectSettings": { + "title": "プロジェクト設定", "edit": "編集", "none": "なし", "install": "インストール", @@ -726,7 +738,7 @@ "repo-not-found": "リポジトリが見つかりません" }, "default-files": { - "create": "プロジェクト関連ファアイルの作成", + "create": "プロジェクト関連ファイルの作成", "desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。", "desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。", "desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。", @@ -742,7 +754,7 @@ "desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。", "desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。", "desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。", - "desc7": "キーはプロジェクトファイルとば別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。", + "desc7": "キーはプロジェクトファイルとは別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。", "credentials": "認証情報", "enable": "暗号化を有効にする", "disable": "暗号化を無効にする", diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index dc37232e2..b94290862 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -14,11 +14,13 @@ "ctrl-e": "core:show-export-dialog", "ctrl-i": "core:show-import-dialog", "ctrl-space": "core:toggle-sidebar", + "ctrl-p": "core:toggle-palette", "ctrl-,": "core:show-user-settings", "ctrl-alt-r": "core:show-remote-diff", "ctrl-alt-n": "core:new-project", "ctrl-alt-o": "core:open-project", - "ctrl-g v": "core:show-version-control-tab" + "ctrl-g v": "core:show-version-control-tab", + "ctrl-shift-l": "core:show-event-log" }, "workspace": { "backspace": "core:delete-selection", diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index d294c8376..7c5bd3dda 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -398,6 +398,10 @@ var RED = (function() { // Refresh flow library to ensure any examples are updated RED.library.loadFlowLibrary(); }); + RED.comms.subscribe("event-log/#", function(topic,payload) { + var id = topic.substring(9); + RED.eventLog.log(id,payload); + }); } function showAbout() { @@ -434,7 +438,9 @@ var RED = (function() { // {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}} // ]}, // null, + {id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true}, {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true}, + {id:"menu-item-event-log",label:RED._("eventLog.title"),onselect:"core:show-event-log"}, null ]}); menuOptions.push(null); @@ -482,6 +488,7 @@ var RED = (function() { RED.library.init(); RED.keyboard.init(); RED.palette.init(); + RED.eventLog.init(); if (RED.settings.theme('palette.editable') !== false) { RED.palette.editor.init(); } else { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index 7252d0998..02736a9b4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -34,14 +34,24 @@ RED.tabs = (function() { if (options.vertical) { wrapper.addClass("red-ui-tabs-vertical"); } - if (options.addButton && typeof options.addButton === 'function') { + if (options.addButton) { wrapper.addClass("red-ui-tabs-add"); var addButton = $('
').appendTo(wrapper); addButton.find('a').click(function(evt) { 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 scrollRight; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 6aab0bf53..5f78a2c74 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -537,6 +537,10 @@ } else { this.selectLabel.text(opt.label); } + if (this.optionMenu) { + this.optionMenu.remove(); + this.optionMenu = null; + } if (opt.options) { if (this.optionExpandButton) { this.optionExpandButton.hide(); @@ -627,10 +631,6 @@ } } } else { - if (this.optionMenu) { - this.optionMenu.remove(); - this.optionMenu = null; - } if (this.optionSelectTrigger) { this.optionSelectTrigger.hide(); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js index 37315974e..c1255137c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js @@ -498,7 +498,7 @@ RED.diff = (function() { nodeDiv.css('backgroundColor',colour); var iconContainer = $('',{class:"palette_icon_container"}).appendTo(nodeDiv); - RED.utils.createIconElement(icon_url, iconContainer, false); + RED.utils.createIconElement(icon_url, iconContainer, false, def, node); return nodeDiv; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index d5f1deeb8..ca8055525 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -808,6 +808,7 @@ RED.editor = (function() { } setToggleState(node.l); + // If a node has icon property in defaults, the icon of the node cannot be modified. (e.g, ui_button node in dashboard) if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) { var iconRow = $('').appendTo(dialogForm); $('