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

Merge branch '0.15.0'

This commit is contained in:
Nick O'Leary 2016-10-09 23:00:28 +01:00
commit f22c3b549e
75 changed files with 5930 additions and 952 deletions

View File

@ -11,13 +11,11 @@ addons:
- gcc-4.8
matrix:
allow_failures:
- node_js: "5"
- node_js: "6"
- node_js: "7"
node_js:
- "7"
- "6"
- "5"
- "4"
- "0.12"
- "0.10"
script:
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage

View File

@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -104,30 +104,35 @@ module.exports = function(grunt) {
"editor/js/settings.js",
"editor/js/user.js",
"editor/js/comms.js",
"editor/js/text/bidi.js",
"editor/js/text/format.js",
"editor/js/ui/state.js",
"editor/js/nodes.js",
"editor/js/history.js",
"editor/js/validators.js",
"editor/js/ui/common/editableList.js",
"editor/js/ui/common/menu.js",
"editor/js/ui/common/popover.js",
"editor/js/ui/common/searchBox.js",
"editor/js/ui/common/tabs.js",
"editor/js/ui/common/typedInput.js",
"editor/js/ui/deploy.js",
"editor/js/ui/menu.js",
"editor/js/ui/keyboard.js",
"editor/js/ui/tabs.js",
"editor/js/ui/popover.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
"editor/js/ui/sidebar.js",
"editor/js/ui/palette.js",
"editor/js/ui/tab-info.js",
"editor/js/ui/tab-config.js",
"editor/js/ui/palette-editor.js",
"editor/js/ui/editor.js",
"editor/js/ui/tray.js",
"editor/js/ui/clipboard.js",
"editor/js/ui/library.js",
"editor/js/ui/notifications.js",
"editor/js/ui/search.js",
"editor/js/ui/subflow.js",
"editor/js/ui/touch/radialMenu.js",
"editor/js/ui/typedInput.js",
"editor/js/ui/editableList.js"
"editor/js/ui/touch/radialMenu.js"
],
dest: "public/red/red.js"
},

BIN
editor/icons/cog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

View File

@ -176,6 +176,7 @@ RED.history = (function() {
n.n.x = n.ox;
n.n.y = n.oy;
n.n.dirty = true;
n.n.changed = n.changed;
}
// A move could have caused a link splice
if (ev.links) {
@ -234,10 +235,6 @@ RED.history = (function() {
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
});
if (ev.node.type === 'subflow') {
$("#menu-item-workspace-menu-"+ev.node.id.replace(".","-")).text(ev.node.name);
}
} else {
RED.editor.updateNodeProperties(ev.node);
RED.editor.validateNode(ev.node);

View File

@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -56,11 +56,9 @@ var RED = (function() {
success: function(data) {
$("body").append(data);
$("body").i18n();
$(".palette-spinner").hide();
$(".palette-scroll").show();
$("#palette-search").show();
$("#palette > .palette-spinner").hide();
$(".palette-scroll").removeClass("hide");
$("#palette-search").removeClass("hide");
loadFlows();
}
});
@ -69,14 +67,19 @@ var RED = (function() {
function loadFlows() {
$.ajax({
headers: {
"Accept":"application/json"
"Accept":"application/json",
},
cache: false,
url: 'flows',
success: function(nodes) {
RED.nodes.import(nodes);
var currentHash = window.location.hash;
RED.nodes.version(nodes.rev);
RED.nodes.import(nodes.flows);
RED.nodes.dirty(false);
RED.view.redraw(true);
if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6));
}
RED.comms.subscribe("status/#",function(topic,msg) {
var parts = topic.split("/");
var node = RED.nodes.node(parts[1]);
@ -169,45 +172,59 @@ var RED = (function() {
}
function loadEditor() {
RED.menu.init({id:"btn-sidemenu",
options: [
{id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
var menuOptions = [];
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid},
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid},
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
{id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[
{id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}},
{id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("ltr")}}},
{id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("rtl")}}},
{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-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
]});
menuOptions.push(null);
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]},
{id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
]});
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
]},
null,
{id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}},
{id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
]});
menuOptions.push(null);
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:RED.search.show});
menuOptions.push(null);
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}});
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
null
]},
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove}
]});
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp},
{id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
]});
menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init();
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:RED.palette.editor.show});
menuOptions.push(null);
}
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp});
menuOptions.push({id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
},
{id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout }
]
});
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout });
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
RED.user.init();
@ -217,6 +234,7 @@ var RED = (function() {
RED.subflow.init();
RED.workspaces.init();
RED.clipboard.init();
RED.search.init();
RED.view.init();
RED.editor.init();

View File

@ -23,21 +23,42 @@ RED.nodes = (function() {
var workspaces = {};
var workspacesOrder =[];
var subflows = {};
var loadedFlowVersion = null;
var pending = {
deleted: {},
added: {}
};
var dirty = false;
function setDirty(d) {
dirty = d;
if (!d) {
pending = {
deleted: {},
added: {}
};
}
RED.events.emit("nodes:change",{dirty:dirty});
}
var registry = (function() {
var moduleList = {};
var nodeList = [];
var nodeSets = {};
var typeToId = {};
var nodeDefinitions = {};
var exports = {
getModule: function(module) {
return moduleList[module];
},
getNodeSetForType: function(nodeType) {
return exports.getNodeSet(typeToId[nodeType]);
},
getModuleList: function() {
return moduleList;
},
getNodeList: function() {
return nodeList;
},
@ -55,27 +76,33 @@ RED.nodes = (function() {
typeToId[ns.types[j]] = ns.id;
}
nodeList.push(ns);
moduleList[ns.module] = moduleList[ns.module] || {
name:ns.module,
version:ns.version,
local:ns.local,
sets:{}
};
moduleList[ns.module].sets[ns.name] = ns;
RED.events.emit("registry:node-set-added",ns);
},
removeNodeSet: function(id) {
var ns = nodeSets[id];
for (var j=0;j<ns.types.length;j++) {
if (ns.added) {
// TODO: too tightly coupled into palette UI
RED.palette.remove(ns.types[j]);
var def = nodeDefinitions[ns.types[j]];
if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def);
}
}
delete typeToId[ns.types[j]];
}
delete nodeSets[id];
for (var i=0;i<nodeList.length;i++) {
if (nodeList[i].id == id) {
if (nodeList[i].id === id) {
nodeList.splice(i,1);
break;
}
}
delete moduleList[ns.module].sets[ns.name];
if (Object.keys(moduleList[ns.module].sets).length === 0) {
delete moduleList[ns.module];
}
RED.events.emit("registry:node-set-removed",ns);
return ns;
},
getNodeSet: function(id) {
@ -84,32 +111,19 @@ RED.nodes = (function() {
enableNodeSet: function(id) {
var ns = nodeSets[id];
ns.enabled = true;
for (var j=0;j<ns.types.length;j++) {
// TODO: too tightly coupled into palette UI
RED.palette.show(ns.types[j]);
var def = nodeDefinitions[ns.types[j]];
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
}
RED.events.emit("registry:node-set-enabled",ns);
},
disableNodeSet: function(id) {
var ns = nodeSets[id];
ns.enabled = false;
for (var j=0;j<ns.types.length;j++) {
// TODO: too tightly coupled into palette UI
RED.palette.hide(ns.types[j]);
var def = nodeDefinitions[ns.types[j]];
if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def);
}
}
RED.events.emit("registry:node-set-disabled",ns);
},
registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def;
if (def.category != "subflows") {
def.set = nodeSets[typeToId[nt]];
nodeSets[typeToId[nt]].added = true;
nodeSets[typeToId[nt]].enabled = true;
var ns;
if (def.set.module === "node-red") {
@ -127,10 +141,7 @@ RED.nodes = (function() {
// TODO: too tightly coupled into palette UI
}
RED.palette.add(nt,def);
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
RED.events.emit("registry:node-type-added",nt);
},
removeNodeType: function(nt) {
if (nt.substring(0,8) != "subflow:") {
@ -138,7 +149,7 @@ RED.nodes = (function() {
throw new Error("this api is subflow only. called with:",nt);
}
delete nodeDefinitions[nt];
RED.palette.remove(nt);
RED.events.emit("registry:node-type-removed",nt);
},
getNodeType: function(nt) {
return nodeDefinitions[nt];
@ -175,6 +186,8 @@ RED.nodes = (function() {
}
nodes.push(n);
}
delete pending.deleted[n.id];
pending.added[n.id] = true;
RED.events.emit('nodes:add',n);
}
function addLink(l) {
@ -240,6 +253,12 @@ RED.nodes = (function() {
if (node && node._def.onremove) {
node._def.onremove.call(n);
}
delete pending.added[id];
pending.deleted[id] = true;
removedNodes.forEach(function(node) {
delete pending.added[node.id];
pending.deleted[node.id] = true;
});
return {links:removedLinks,nodes:removedNodes};
}
@ -252,6 +271,8 @@ RED.nodes = (function() {
function addWorkspace(ws) {
workspaces[ws.id] = ws;
pending.added[ws.id] = true;
delete pending.deleted[ws.id];
ws._def = {
defaults: {
label: {value:""}
@ -289,6 +310,8 @@ RED.nodes = (function() {
var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links);
}
pending.deleted[id] = true;
delete pending.added[id]
return {nodes:removedNodes,links:removedLinks};
}
@ -309,8 +332,17 @@ RED.nodes = (function() {
});
sf.name = subflowName;
}
sf._def = {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9",
inputs: sf.in.length,
outputs: sf.out.length
}
subflows[sf.id] = sf;
delete pending.deleted[sf.id];
pending.added[sf.id] = true;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
info: sf.info,
@ -334,6 +366,8 @@ RED.nodes = (function() {
}
function removeSubflow(sf) {
delete subflows[sf.id];
delete pending.added[sf.id];
pending.deleted[sf.id] = true;
registry.removeNodeType("subflow:"+sf.id);
}
@ -419,11 +453,12 @@ RED.nodes = (function() {
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
if (n._def.credentials[cred].type == 'password') {
if (n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
if (!n.credentials._ ||
n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
(n.credentials["has_"+cred] && n.credentials[cred])) {
credentialSet[cred] = n.credentials[cred];
}
} else if (n.credentials[cred] != null && n.credentials[cred] != n.credentials._[cred]) {
} else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) {
credentialSet[cred] = n.credentials[cred];
}
}
@ -537,7 +572,10 @@ RED.nodes = (function() {
}
//TODO: rename this (createCompleteNodeSet)
function createCompleteNodeSet() {
function createCompleteNodeSet(exportCredentials) {
if (exportCredentials === undefined) {
exportCredentials = true;
}
var nns = [];
var i;
for (i=0;i<workspacesOrder.length;i++) {
@ -552,16 +590,55 @@ RED.nodes = (function() {
}
for (i in configNodes) {
if (configNodes.hasOwnProperty(i)) {
nns.push(convertNode(configNodes[i], true));
nns.push(convertNode(configNodes[i], exportCredentials));
}
}
for (i=0;i<nodes.length;i++) {
var node = nodes[i];
nns.push(convertNode(node, true));
nns.push(convertNode(node, exportCredentials));
}
return nns;
}
function checkForMatchingSubflow(subflow,subflowNodes) {
var i;
var match = null;
try {
RED.nodes.eachSubflow(function(sf) {
if (sf.name != subflow.name ||
sf.info != subflow.info ||
sf.in.length != subflow.in.length ||
sf.out.length != subflow.out.length) {
return;
}
var sfNodes = RED.nodes.filterNodes({z:sf.id});
if (sfNodes.length != subflowNodes.length) {
return;
}
var subflowNodeSet = [subflow].concat(subflowNodes);
var sfNodeSet = [sf].concat(sfNodes);
var exportableSubflowNodes = JSON.stringify(subflowNodeSet);
var exportableSFNodes = JSON.stringify(createExportableNodeSet(sfNodeSet));
var nodeMap = {};
for (i=0;i<sfNodes.length;i++) {
exportableSubflowNodes = exportableSubflowNodes.replace(new RegExp("\""+subflowNodes[i].id+"\"","g"),'"'+sfNodes[i].id+'"');
}
exportableSubflowNodes = exportableSubflowNodes.replace(new RegExp("\""+subflow.id+"\"","g"),'"'+sf.id+'"');
if (exportableSubflowNodes !== exportableSFNodes) {
return;
}
match = sf;
throw new Error();
});
} catch(err) {
console.log(err.stack);
}
return match;
}
function compareNodes(nodeA,nodeB,idMustMatch) {
if (idMustMatch && nodeA.id != nodeB.id) {
return false;
@ -591,10 +668,11 @@ RED.nodes = (function() {
return true;
}
function importNodes(newNodesObj,createNewIds) {
function importNodes(newNodesObj,createNewIds,createMissingWorkspace) {
var i;
var n;
var newNodes;
var nodeZmap = {};
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
@ -625,6 +703,11 @@ RED.nodes = (function() {
unknownTypes.indexOf(n.type)==-1) {
unknownTypes.push(n.type);
}
if (n.z) {
nodeZmap[n.z] = nodeZmap[n.z] || [];
nodeZmap[n.z].push(n);
}
}
if (unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
@ -659,12 +742,15 @@ RED.nodes = (function() {
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var subflow_blacklist = {};
var node_map = {};
var new_nodes = [];
var new_links = [];
var nid;
var def;
var configNode;
var missingWorkspace = null;
var d;
// Find all tabs and subflow templates
for (i=0;i<newNodes.length;i++) {
@ -686,6 +772,10 @@ RED.nodes = (function() {
RED.workspaces.add(n);
new_workspaces.push(n);
} else if (n.type === "subflow") {
var matchingSubflow = checkForMatchingSubflow(n,nodeZmap[n.id]);
if (matchingSubflow) {
subflow_blacklist[n.id] = matchingSubflow;
} else {
subflow_map[n.id] = n;
if (createNewIds) {
nid = getID();
@ -710,6 +800,7 @@ RED.nodes = (function() {
addSubflow(n,createNewIds);
}
}
}
// Add a tab if there isn't one there already
if (defaultWorkspace == null) {
@ -728,15 +819,25 @@ RED.nodes = (function() {
var existingConfigNode = null;
if (createNewIds) {
if (n.z) {
if (subflow_map[n.z]) {
if (subflow_blacklist[n.z]) {
continue;
} else if (subflow_map[n.z]) {
n.z = subflow_map[n.z].id;
} else {
n.z = workspace_map[n.z];
if (!workspaces[n.z]) {
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);
new_workspaces.push(missingWorkspace);
}
n.z = missingWorkspace.id;
} else {
n.z = activeWorkspace;
}
}
}
}
existingConfigNode = RED.nodes.node(n.id);
if (existingConfigNode) {
if (n.z && existingConfigNode.z !== n.z) {
@ -757,10 +858,19 @@ RED.nodes = (function() {
}
if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) {
configNode = {id:n.id, z:n.z, type:n.type, users:[]};
for (var d in def.defaults) {
configNode = {id:n.id, z:n.z, type:n.type, users:[], _config:{}};
for (d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
configNode._config[d] = JSON.stringify(n[d]);
}
}
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
configNode.credentials = {};
for (d in def.credentials) {
if (def.credentials.hasOwnProperty(d) && n.credentials.hasOwnProperty(d)) {
configNode.credentials[d] = n.credentials[d];
}
}
}
configNode.label = def.label;
@ -782,28 +892,46 @@ RED.nodes = (function() {
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
def = registry.getNodeType(n.type);
if (!def || def.category != "config") {
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false,_config:{}};
if (createNewIds) {
if (subflow_map[node.z]) {
if (subflow_blacklist[n.z]) {
continue;
} else if (subflow_map[node.z]) {
node.z = subflow_map[node.z].id;
} else {
node.z = workspace_map[node.z];
if (!workspaces[node.z]) {
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);
new_workspaces.push(missingWorkspace);
}
node.z = missingWorkspace.id;
} else {
node.z = activeWorkspace;
}
}
}
node.id = getID();
} else {
node.id = n.id;
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);
new_workspaces.push(missingWorkspace);
}
node.z = missingWorkspace.id;
} else {
node.z = activeWorkspace;
}
}
}
node.type = n.type;
node._def = def;
if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_map[parentId]||getSubflow(parentId);
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
parentId = subflow.id;
node.type = "subflow:"+parentId;
@ -844,9 +972,20 @@ RED.nodes = (function() {
if (node._def.category != "config") {
node.inputs = n.inputs||node._def.inputs;
node.outputs = n.outputs||node._def.outputs;
for (var d2 in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d2)) {
node[d2] = n[d2];
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
node[d] = n[d];
node._config[d] = JSON.stringify(n[d]);
}
}
node._config.x = node.x;
node._config.y = node.y;
if (node._def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
node.credentials = {};
for (d in node._def.credentials) {
if (node._def.credentials.hasOwnProperty(d) && n.credentials.hasOwnProperty(d)) {
node.credentials[d] = n.credentials[d];
}
}
}
}
@ -943,7 +1082,7 @@ RED.nodes = (function() {
}
RED.workspaces.refresh();
return [new_nodes,new_links,new_workspaces,new_subflows];
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace];
}
// TODO: supports filter.z|type
@ -1011,6 +1150,14 @@ RED.nodes = (function() {
}
}
function flowVersion(version) {
if (version !== undefined) {
loadedFlowVersion = version;
} else {
return loadedFlowVersion;
}
}
return {
registry:registry,
setNodeList: registry.setNodeList,
@ -1074,11 +1221,15 @@ RED.nodes = (function() {
node: getNode,
version: flowVersion,
filterNodes: filterNodes,
filterLinks: filterLinks,
import: importNodes,
pending: function() { return pending },
getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet,

View File

@ -84,6 +84,7 @@ RED.settings = (function () {
if (auth_tokens) {
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
}
jqXHR.setRequestHeader("Node-RED-API-Version","v2");
}
}
});

130
editor/js/text/bidi.js Normal file
View File

@ -0,0 +1,130 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.text = {};
RED.text.bidi = (function() {
var textDir = "";
var LRE = "\u202A",
RLE = "\u202B",
PDF = "\u202C";
function isRTLValue(stringValue) {
var length = stringValue.length;
for (var i=0;i<length;i++) {
if (isBidiChar(stringValue.charCodeAt(i))) {
return true;
}
else if(isLatinChar(stringValue.charCodeAt(i))) {
return false;
}
}
return false;
}
function isBidiChar(c) {
return (c >= 0x05d0 && c <= 0x05ff)||
(c >= 0x0600 && c <= 0x065f)||
(c >= 0x066a && c <= 0x06ef)||
(c >= 0x06fa && c <= 0x07ff)||
(c >= 0xfb1d && c <= 0xfdff)||
(c >= 0xfe70 && c <= 0xfefc);
}
function isLatinChar(c){
return (c > 64 && c < 91)||(c > 96 && c < 123)
}
/**
* Determines the text direction of a given string.
* @param value - the string
*/
function resolveBaseTextDir(value) {
if (textDir == "auto") {
if (isRTLValue(value)) {
return "rtl";
} else {
return "ltr";
}
}
else {
return textDir;
}
}
function onInputChange() {
$(this).attr("dir", resolveBaseTextDir($(this).val()));
}
/**
* Adds event listeners to the Input to ensure its text-direction attribute
* is properly set based on its content.
* @param input - the input field
*/
function prepareInput(input) {
input.on("keyup",onInputChange).on("paste",onInputChange).on("cut",onInputChange);
// Set the initial text direction
onInputChange.call(input);
}
/**
* Enforces the text direction of a given string by adding
* UCC (Unicode Control Characters)
* @param value - the string
*/
function enforceTextDirectionWithUCC(value) {
if (value) {
var dir = resolveBaseTextDir(value);
if (dir == "ltr") {
return LRE + value + PDF;
}
else if (dir == "rtl") {
return RLE + value + PDF;
}
}
return value;
}
/**
* Enforces the text direction for all the spans with style bidiAware under
* workspace or sidebar div
*/
function enforceTextDirectionOnPage() {
$("#workspace").find('span.bidiAware').each(function() {
$(this).attr("dir", resolveBaseTextDir($(this).html()));
});
$("#sidebar").find('span.bidiAware').each(function() {
$(this).attr("dir", resolveBaseTextDir($(this).text()));
});
}
/**
* Sets the text direction preference
* @param dir - the text direction preference
*/
function setTextDirection(dir) {
textDir = dir;
RED.nodes.eachNode(function(n) { n.dirty = true;});
RED.view.redraw();
RED.palette.refresh();
enforceTextDirectionOnPage();
}
return {
setTextDirection: setTextDirection,
enforceTextDirectionWithUCC: enforceTextDirectionWithUCC,
resolveBaseTextDir: resolveBaseTextDir,
prepareInput: prepareInput
}
})();

1330
editor/js/text/format.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -46,12 +46,24 @@ RED.clipboard = (function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-copy",
class: "primary",
text: RED._("clipboard.export.copy"),
click: function() {
$("#clipboard-export").select();
document.execCommand("copy");
document.getSelection().removeAllRanges();
RED.notify(RED._("clipboard.nodesExported"));
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-ok",
class: "primary",
text: RED._("common.label.import"),
click: function() {
RED.view.importNodes($("#clipboard-import").val());
RED.view.importNodes($("#clipboard-import").val(),$("#import-tab > a.selected").attr('id') === 'import-tab-new');
$( this ).dialog( "close" );
}
}
@ -65,18 +77,36 @@ RED.clipboard = (function() {
dialogContainer = dialog.children(".dialog-form");
exportNodesDialog = '<div class="form-row">'+
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> '+RED._("clipboard.nodes")+'</label>'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
exportNodesDialog =
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.export.copy"></label>'+
'<span id="export-range-group" class="button-group">'+
'<a id="export-range-selected" class="editor-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
'<a id="export-range-flow" class="editor-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="export-range-full" class="editor-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
'</span>'+
'</div>'+
'<div class="form-tips">'+
RED._("clipboard.selectNodes")+
'<div class="form-row">'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 4px;font-family: monospace; font-size: 12px; background:#f3f3f3; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-row" style="text-align: right;">'+
'<span id="export-format-group" class="button-group">'+
'<a id="export-format-mini" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
'<a id="export-format-full" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
'</span>'+
'</div>';
importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
RED._("clipboard.pasteNodes")+
'"></textarea>'+
'</div>'+
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+
'<span id="import-tab" class="button-group">'+
'<a id="import-tab-current" class="editor-button toggle selected" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="import-tab-new" class="editor-button toggle" href="#" data-i18n="clipboard.import.newFlow"></a>'+
'</span>'+
'</div>';
}
@ -100,32 +130,108 @@ RED.clipboard = (function() {
function importNodes() {
dialogContainer.empty();
dialogContainer.append($(importNodesDialog));
dialogContainer.i18n();
$("#clipboard-dialog-ok").show();
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-close").hide();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-ok").button("disable");
$("#clipboard-import").keyup(validateImport);
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
$("#import-tab > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
});
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
}
function exportNodes() {
dialogContainer.empty();
dialogContainer.append($(exportNodesDialog));
dialogContainer.i18n();
$("#export-format-group > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var flow = $("#clipboard-export").val();
if (flow.length > 0) {
var nodes = JSON.parse(flow);
var format = $(this).attr('id');
if (format === 'export-format-full') {
flow = JSON.stringify(nodes,null,4);
} else {
flow = JSON.stringify(nodes);
}
$("#clipboard-export").val(flow);
}
});
$("#export-range-group > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var type = $(this).attr('id');
var flow = "";
var nodes = null;
if (type === 'export-range-selected') {
var selection = RED.view.selection();
nodes = RED.nodes.createExportableNodeSet(selection.nodes);
} else if (type === 'export-range-flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace});
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
} else if (type === 'export-range-full') {
nodes = RED.nodes.createCompleteNodeSet(false);
}
if (nodes !== null) {
if (RED.settings.flowFilePretty) {
flow = JSON.stringify(nodes,null,4);
} else {
flow = JSON.stringify(nodes);
}
}
if (flow.length > 0) {
$("#export-copy").removeClass('disabled');
} else {
$("#export-copy").addClass('disabled');
}
$("#clipboard-export").val(flow);
})
$("#clipboard-dialog-ok").hide();
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-close").show();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-close").hide();
var selection = RED.view.selection();
if (selection.nodes) {
var nns = RED.nodes.createExportableNodeSet(selection.nodes);
if (RED.settings.flowFilePretty) {
nns = JSON.stringify(nns,null,4);
$("#export-range-selected").click();
} else {
nns = JSON.stringify(nns);
$("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click();
}
if (RED.settings.flowFilePretty) {
$("#export-format-full").click();
} else {
$("#export-format-mini").click();
}
$("#clipboard-export")
.val(nns)
.focus(function() {
var textarea = $(this);
textarea.select();
@ -135,7 +241,18 @@ RED.clipboard = (function() {
})
});
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
setTimeout(function() {
$("#clipboard-export").focus();
if (!document.queryCommandEnabled("copy")) {
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-close").show();
} else {
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-copy").show();
}
},0);
}
function hideDropTarget() {

View File

@ -66,11 +66,30 @@
that.addItem({});
});
}
if (this.element.css("position") === "absolute") {
["top","left","bottom","right"].forEach(function(s) {
var v = that.element.css(s);
if (s!=="auto" && s!=="") {
that.topContainer.css(s,v);
that.uiContainer.css(s,"0");
that.element.css(s,'auto');
}
})
this.element.css("position","static");
this.topContainer.css("position","absolute");
this.uiContainer.css("position","absolute");
}
this.uiContainer.addClass("red-ui-editableList-container");
this.uiHeight = this.element.height();
this.activeFilter = this.options.filter||null;
this.activeSort = this.options.sort||null;
this.scrollOnAdd = this.options.scrollOnAdd;
if (this.scrollOnAdd === undefined) {
this.scrollOnAdd = true;
}
var minHeight = this.element.css("minHeight");
if (minHeight !== '0px') {
this.uiContainer.css("minHeight",minHeight);
@ -141,6 +160,42 @@
},
_destroy: function() {
},
_refreshFilter: function() {
var that = this;
var count = 0;
if (!this.activeFilter) {
this.element.children().show();
}
var items = this.items();
items.each(function (i,el) {
var data = el.data('data');
try {
if (that.activeFilter(data)) {
el.parent().show();
count++;
} else {
el.parent().hide();
}
} catch(err) {
console.log(err);
el.parent().show();
count++;
}
});
return count;
},
_refreshSort: function() {
if (this.activeSort) {
var items = this.element.children();
var that = this;
items.sort(function(A,B) {
return that.activeSort($(A).find(".red-ui-editableList-item-content").data('data'),$(B).find(".red-ui-editableList-item-content").data('data'));
});
$.each(items,function(idx,li) {
that.element.append(li);
})
}
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
@ -152,7 +207,23 @@
addItem: function(data) {
var that = this;
data = data || {};
var li = $('<li>').appendTo(this.element);
var li = $('<li>');
var added = false;
if (this.activeSort) {
var items = this.items();
var skip = false;
items.each(function(i,el) {
if (added) { return }
var itemData = el.data('data');
if (that.activeSort(data,itemData) < 0) {
li.insertBefore(el.closest("li"));
added = true;
}
});
}
if (!added) {
li.appendTo(this.element);
}
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
row.data('data',data);
if (this.options.sortable === true) {
@ -178,9 +249,20 @@
var index = that.element.children().length-1;
setTimeout(function() {
that.options.addItem(row,index,data);
if (that.activeFilter) {
try {
if (!that.activeFilter(data)) {
li.hide();
}
} catch(err) {
}
}
if (!that.activeSort && that.scrollOnAdd) {
setTimeout(function() {
that.uiContainer.scrollTop(that.element.height());
},0);
}
},0);
}
},
@ -198,6 +280,21 @@
},
empty: function() {
this.element.empty();
},
filter: function(filter) {
if (filter !== undefined) {
this.activeFilter = filter;
}
return this._refreshFilter();
},
sort: function(sort) {
if (sort !== undefined) {
this.activeSort = sort;
}
return this._refreshSort();
},
length: function() {
return this.element.children().length;
}
});
})(jQuery);

View File

@ -0,0 +1,96 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.
**/
(function($) {
$.widget( "nodered.searchBox", {
_create: function() {
var that = this;
this.currentTimeout = null;
this.lastSent = "";
this.element.val("");
this.uiContainer = this.element.wrap("<div>").parent();
this.uiContainer.addClass("red-ui-searchBox-container");
$('<i class="fa fa-search"></i>').prependTo(this.uiContainer);
this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer);
this.clearButton.on("click",function(e) {
e.preventDefault();
that.element.val("");
that._change("",true);
that.element.focus();
});
this.resultCount = $('<span>',{class:"red-ui-searchBox-resultCount hide"}).appendTo(this.uiContainer);
this.element.val("");
this.element.on("keydown",function(evt) {
if (evt.keyCode === 27) {
that.element.val("");
}
})
this.element.on("keyup",function(evt) {
that._change($(this).val());
});
this.element.on("focus",function() {
$("body").one("mousedown",function() {
that.element.blur();
});
});
},
_change: function(val,instant) {
var fireEvent = false;
if (val === "") {
this.clearButton.hide();
fireEvent = true;
} else {
this.clearButton.show();
fireEvent = (val.length >= (this.options.minimumLength||0));
}
var current = this.element.val();
fireEvent = fireEvent && current !== this.lastSent;
if (fireEvent) {
if (!instant && this.options.delay > 0) {
clearTimeout(this.currentTimeout);
var that = this;
this.currentTimeout = setTimeout(function() {
that.lastSent = that.element.val();
that._trigger("change");
},this.options.delay);
} else {
this._trigger("change");
}
}
},
value: function(val) {
if (val === undefined) {
return this.element.val();
} else {
this.element.val(val);
this._change(val);
}
},
count: function(val) {
if (val === undefined || val === null || val === "") {
this.resultCount.text("").hide();
} else {
this.resultCount.text(val).show();
}
}
});
})(jQuery);

View File

@ -17,15 +17,58 @@
RED.tabs = (function() {
function createTabs(options) {
var tabs = {};
var currentTabWidth;
var currentActiveTabWidth = 0;
var ul = $("#"+options.id);
ul.addClass("red-ui-tabs");
var wrapper = ul.wrap( "<div>" ).parent();
var scrollContainer = ul.wrap( "<div>" ).parent();
wrapper.addClass("red-ui-tabs");
if (options.addButton && typeof options.addButton === 'function') {
wrapper.addClass("red-ui-tabs-add");
var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
addButton.find('a').click(function(evt) {
evt.preventDefault();
options.addButton();
})
}
var scrollLeft;
var scrollRight;
if (options.scrollable) {
wrapper.addClass("red-ui-tabs-scrollable");
scrollContainer.addClass("red-ui-tabs-scroll-container");
scrollContainer.scroll(updateScroll);
scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();});
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
}
function scrollEventHandler(evt,dir) {
evt.preventDefault();
if ($(this).hasClass('disabled')) {
return;
}
var currentScrollLeft = scrollContainer.scrollLeft();
scrollContainer.animate( { scrollLeft: dir }, 300);
var interval = setInterval(function() {
var newScrollLeft = scrollContainer.scrollLeft()
if (newScrollLeft === currentScrollLeft) {
clearInterval(interval);
return;
}
currentScrollLeft = newScrollLeft;
scrollContainer.animate( { scrollLeft: dir }, 300);
},300);
$(this).one('mouseup',function() {
clearInterval(interval);
})
}
ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab");
@ -34,6 +77,23 @@ RED.tabs = (function() {
return false;
}
function updateScroll() {
if (ul.children().length !== 0) {
var sl = scrollContainer.scrollLeft();
var scWidth = scrollContainer.width();
var ulWidth = ul.width();
if (sl === 0) {
scrollLeft.hide();
} else {
scrollLeft.show();
}
if (sl === ulWidth-scWidth) {
scrollRight.hide();
} else {
scrollRight.show();
}
}
}
function onTabDblClick() {
if (options.ondblclick) {
options.ondblclick(tabs[$(this).attr('href').slice(1)]);
@ -49,6 +109,14 @@ RED.tabs = (function() {
ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"});
link.parent().addClass("active");
if (options.scrollable) {
var pos = link.parent().position().left;
if (pos-21 < 0) {
scrollContainer.animate( { scrollLeft: '+='+(pos-50) }, 300);
} else if (pos + 120 > scrollContainer.width()) {
scrollContainer.animate( { scrollLeft: '+='+(pos + 140-scrollContainer.width()) }, 300);
}
}
if (options.onchange) {
options.onchange(tabs[link.attr('href').slice(1)]);
}
@ -61,23 +129,29 @@ RED.tabs = (function() {
function updateTabWidths() {
var tabs = ul.find("li.red-ui-tab");
var width = ul.width();
var width = wrapper.width();
var tabCount = tabs.size();
var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = 100*tabWidth/width;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = currentTabWidth+"%";
if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (options.scrollable) {
tabWidth = Math.max(tabWidth,140);
currentTabWidth = tabWidth+"px";
currentActiveTabWidth = 0;
var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount);
ul.width(listWidth);
updateScroll();
} else if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (tabWidth < options.minimumActiveTabWidth) {
tabCount -= 1;
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
currentTabWidth = 100*tabWidth/width;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
} else {
currentActiveTabWidth = 0;
}
}
tabs.css({width:currentTabWidth+"%"});
tabs.css({width:currentTabWidth});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide();
@ -97,7 +171,9 @@ RED.tabs = (function() {
}
ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick);
setTimeout(function() {
updateTabWidths();
},0);
function removeTab(id) {
@ -126,7 +202,8 @@ RED.tabs = (function() {
if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
}
$('<span/>').text(tab.label).appendTo(link);
var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link);
span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label));
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
@ -187,8 +264,8 @@ RED.tabs = (function() {
}
},
drag: function(event,ui) {
ui.position.left += tabElements[tabDragIndex].left;
var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2;
ui.position.left += tabElements[tabDragIndex].left+scrollContainer.scrollLeft();
var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2 - scrollContainer.scrollLeft();
for (var i=0;i<tabElements.length;i++) {
if (i === tabDragIndex) {
continue;
@ -209,8 +286,6 @@ RED.tabs = (function() {
break;
}
}
// console.log(ui.position.left,ui.offset.left);
},
stop: function(event,ui) {
ul.children().css({position:"relative",left:"",transition:""});
@ -239,7 +314,7 @@ RED.tabs = (function() {
tabs[id].label = label;
var tab = ul.find("a[href='#"+id+"']");
tab.attr("title",label);
tab.find("span").text(label);
tab.find("span").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
updateTabWidths();
},
order: function(order) {

View File

@ -35,6 +35,7 @@ RED.deploy = (function() {
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
}
var currentDiff = null;
/**
* options:
@ -76,7 +77,7 @@ RED.deploy = (function() {
$('#btn-deploy').click(function() { save(); });
$( "#node-dialog-confirm-deploy" ).dialog({
title: "Confirm deploy",
title: RED._('deploy.confirm.button.confirm'),
modal: true,
autoOpen: false,
width: 550,
@ -88,6 +89,15 @@ RED.deploy = (function() {
$( this ).dialog( "close" );
}
},
// {
// id: "node-dialog-confirm-deploy-review",
// text: RED._("deploy.confirm.button.review"),
// class: "primary",
// click: function() {
// showDiff();
// $( this ).dialog( "close" );
// }
// },
{
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
@ -97,7 +107,7 @@ RED.deploy = (function() {
if (ignoreChecked) {
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
}
save(true);
save(true,$( "#node-dialog-confirm-deploy-type" ).val() === "conflict");
$( this ).dialog( "close" );
}
}
@ -109,6 +119,15 @@ RED.deploy = (function() {
'<label style="display:inline;" for="node-dialog-confirm-deploy-hide"> do not warn about this again</label>'+
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
'</div>');
},
open: function() {
if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") {
// $("#node-dialog-confirm-deploy-review").show();
$("#node-dialog-confirm-deploy-hide").parent().hide();
} else {
// $("#node-dialog-confirm-deploy-review").hide();
$("#node-dialog-confirm-deploy-hide").parent().show();
}
}
});
@ -123,6 +142,199 @@ RED.deploy = (function() {
$("#btn-deploy").addClass("disabled");
}
});
// $("#node-dialog-view-diff").dialog({
// title: RED._('deploy.confirm.button.review'),
// modal: true,
// autoOpen: false,
// buttons: [
// {
// text: RED._("deploy.confirm.button.cancel"),
// click: function() {
// $( this ).dialog( "close" );
// }
// },
// {
// text: RED._("deploy.confirm.button.merge"),
// class: "primary",
// click: function() {
// $( this ).dialog( "close" );
// }
// }
// ],
// open: function() {
// $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)});
// }
// });
// $("#node-dialog-view-diff-diff").editableList({
// addButton: false,
// scrollOnAdd: false,
// addItem: function(container,i,object) {
// var tab = object.tab.n;
// var tabDiv = $('<div>',{class:"node-diff-tab collapsed"}).appendTo(container);
//
// var titleRow = $('<div>',{class:"node-diff-tab-title"}).appendTo(tabDiv);
// titleRow.click(function(evt) {
// evt.preventDefault();
// titleRow.parent().toggleClass('collapsed');
// })
// var chevron = $('<i class="fa fa-angle-down node-diff-chevron ">').appendTo(titleRow);
// var title = $('<span>').html(tab.label||tab.id).appendTo(titleRow);
//
// var stats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(titleRow);
//
// var addedCount = 0;
// var deletedCount = 0;
// var changedCount = 0;
// var conflictedCount = 0;
//
// object.tab.nodes.forEach(function(node) {
// var realNode = RED.nodes.node(node.id);
// var hasChanges = false;
// if (currentDiff.added[node.id]) {
// addedCount++;
// hasChanges = true;
// }
// if (currentDiff.deleted[node.id]) {
// deletedCount++;
// hasChanges = true;
// }
// if (currentDiff.changed[node.id]) {
// changedCount++;
// hasChanges = true;
// }
// if (currentDiff.conflicted[node.id]) {
// conflictedCount++;
// hasChanges = true;
// }
//
// if (hasChanges) {
// var def = RED.nodes.getType(node.type)||{};
// var div = $("<div>",{class:"node-diff-node-entry collapsed"}).appendTo(tabDiv);
// var nodeTitleDiv = $("<div>",{class:"node-diff-node-entry-title"}).appendTo(div);
// nodeTitleDiv.click(function(evt) {
// evt.preventDefault();
// $(this).parent().toggleClass('collapsed');
// })
// var newNode = currentDiff.newConfig.all[node.id];
// var nodePropertiesDiv = $("<div>",{class:"node-diff-node-entry-properties"}).appendTo(div);
//
// var nodePropertiesTable = $("<table>").appendTo(nodePropertiesDiv);
//
// if (node.hasOwnProperty('x')) {
// if (newNode.x !== node.x || newNode.y !== node.y) {
// var currentPosition = node.x+", "+node.y
// var newPosition = newNode.x+", "+newNode.y;
// $("<tr><td>position</td><td>"+currentPosition+"</td><td>"+newPosition+"</td></tr>").appendTo(nodePropertiesTable);
// }
// }
// var properties = Object.keys(node).filter(function(p) { return p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
// if (def.defaults) {
// properties = properties.concat(Object.keys(def.defaults));
// }
// properties.forEach(function(d) {
// var localValue = JSON.stringify(node[d]);
// var remoteValue = JSON.stringify(newNode[d]);
// var originalValue = realNode._config[d];
//
// if (remoteValue !== originalValue) {
// var formattedProperty = formatNodeProperty(node[d]);
// var newFormattedProperty = formatNodeProperty(newNode[d]);
// if (localValue === originalValue) {
// // no conflict change
// } else {
// // conflicting change
// }
// $("<tr><td>"+d+'</td><td class="">'+formattedProperty+'</td><td class="node-diff-property-changed">'+newFormattedProperty+"</td></tr>").appendTo(nodePropertiesTable);
// }
//
// })
// var nodeChevron = $('<i class="fa fa-angle-down node-diff-chevron">').appendTo(nodeTitleDiv);
//
//
// // var leftColumn = $('<div>',{class:"node-diff-column"}).appendTo(div);
// // var rightColumn = $('<div>',{class:"node-diff-column"}).appendTo(div);
// // rightColumn.html("&nbsp");
//
//
//
// var nodeDiv = $("<div>",{class:"node-diff-node-entry-node"}).appendTo(nodeTitleDiv);
// var colour = def.color;
// var icon_url = "arrow-in.png";
// if (node.type === 'tab') {
// colour = "#C0DEED";
// icon_url = "subflow.png";
// } else if (def.category === 'config') {
// icon_url = "cog.png";
// } else if (node.type === 'unknown') {
// icon_url = "alert.png";
// } else {
// icon_url = def.icon;
// }
// nodeDiv.css('backgroundColor',colour);
//
// var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
// $('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
//
//
//
// var contentDiv = $('<div>',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv);
//
// $('<span>',{class:"node-diff-node-label"}).html(node.label || node.name || node.id).appendTo(contentDiv);
// //$('<div>',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv);
// //$('<div>',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv);
// }
//
// });
//
// var statsInfo = '<span class="node-diff-count">'+object.tab.nodes.length+" nodes"+
// (addedCount+deletedCount+changedCount+conflictedCount > 0 ? " : ":"")+
// "</span> "+
// ((addedCount > 0)?'<span class="node-diff-added">'+addedCount+' added</span> ':'')+
// ((deletedCount > 0)?'<span class="node-diff-deleted">'+deletedCount+' deleted</span> ':'')+
// ((changedCount > 0)?'<span class="node-diff-changed">'+changedCount+' changed</span> ':'')+
// ((conflictedCount > 0)?'<span class="node-diff-conflicted">'+conflictedCount+' conflicts</span>':'');
// stats.html(statsInfo);
//
//
//
// //
// //
// //
// // var node = object.node;
// // var realNode = RED.nodes.node(node.id);
// // var def = RED.nodes.getType(object.node.type)||{};
// // var l = "";
// // if (def && def.label && realNode) {
// // l = def.label;
// // try {
// // l = (typeof l === "function" ? l.call(realNode) : l);
// // } catch(err) {
// // console.log("Definition error: "+node.type+".label",err);
// // }
// // }
// // l = l||node.label||node.name||node.id||"";
// // console.log(node);
// // var div = $('<div>').appendTo(container);
// // div.html(l);
// }
// });
}
function formatNodeProperty(prop) {
var formattedProperty = prop;
if (formattedProperty === null) {
formattedProperty = 'null';
} else if (formattedProperty === undefined) {
formattedProperty = 'undefined';
} else if (typeof formattedProperty === 'object') {
formattedProperty = JSON.stringify(formattedProperty);
}
if (/\n/.test(formattedProperty)) {
formattedProperty = "<pre>"+formattedProperty+"</pre>"
}
return formattedProperty;
}
function getNodeInfo(node) {
@ -160,11 +372,157 @@ RED.deploy = (function() {
return 0;
}
function save(force) {
function resolveConflict(currentNodes) {
$( "#node-dialog-confirm-deploy-config" ).hide();
$( "#node-dialog-confirm-deploy-unknown" ).hide();
$( "#node-dialog-confirm-deploy-unused" ).hide();
$( "#node-dialog-confirm-deploy-conflict" ).show();
$( "#node-dialog-confirm-deploy-type" ).val("conflict");
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
// $("#node-dialog-confirm-deploy-review").append($('<img src="red/images/spin.svg" style="background: rgba(255,255,255,0.8); margin-top: -16px; margin-left: -8px; height:16px; position: absolute; "/>'));
// $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",0.4);
// $("#node-dialog-confirm-deploy-review").attr("disabled",true).addClass("disabled");
// $.ajax({
// headers: {
// "Accept":"application/json",
// },
// cache: false,
// url: 'flows',
// success: function(nodes) {
// var newNodes = nodes.flows;
// var newRevision = nodes.rev;
// generateDiff(currentNodes,newNodes);
// $("#node-dialog-confirm-deploy-review").attr("disabled",false).removeClass("disabled");
// $("#node-dialog-confirm-deploy-review img").remove();
// $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",1);
// }
// });
}
// function parseNodes(nodeList) {
// var tabOrder = [];
// var tabs = {};
// var subflows = {};
// var globals = [];
// var all = {};
//
// nodeList.forEach(function(node) {
// all[node.id] = node;
// if (node.type === 'tab') {
// tabOrder.push(node.id);
// tabs[node.id] = {n:node,nodes:[]};
// } else if (node.type === 'subflow') {
// subflows[node.id] = {n:node,nodes:[]};
// }
// });
//
// nodeList.forEach(function(node) {
// if (node.type !== 'tab' && node.type !== 'subflow') {
// if (tabs[node.z]) {
// tabs[node.z].nodes.push(node);
// } else if (subflows[node.z]) {
// subflows[node.z].nodes.push(node);
// } else {
// globals.push(node);
// }
// }
// });
//
// return {
// all: all,
// tabOrder: tabOrder,
// tabs: tabs,
// subflows: subflows,
// globals: globals
// }
// }
// function generateDiff(currentNodes,newNodes) {
// var currentConfig = parseNodes(currentNodes);
// var newConfig = parseNodes(newNodes);
// var pending = RED.nodes.pending();
// var added = {};
// var deleted = {};
// var changed = {};
// var conflicted = {};
//
//
// Object.keys(currentConfig.all).forEach(function(id) {
// var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
// if (!newConfig.all.hasOwnProperty(id)) {
// if (!pending.added.hasOwnProperty(id)) {
// deleted[id] = true;
// conflicted[id] = node.changed;
// }
// } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
// changed[id] = true;
// conflicted[id] = node.changed;
// }
// });
// Object.keys(newConfig.all).forEach(function(id) {
// if (!currentConfig.all.hasOwnProperty(id) && !pending.deleted.hasOwnProperty(id)) {
// added[id] = true;
// }
// });
//
// // console.log("Added",added);
// // console.log("Deleted",deleted);
// // console.log("Changed",changed);
// // console.log("Conflicted",conflicted);
//
// var formatString = function(id) {
// return conflicted[id]?"!":(added[id]?"+":(deleted[id]?"-":(changed[id]?"~":" ")));
// }
// newConfig.tabOrder.forEach(function(tabId) {
// var tab = newConfig.tabs[tabId];
// console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")");
// tab.nodes.forEach(function(node) {
// console.log(" ",formatString(node.id),node.type,node.name || node.id);
// })
// if (currentConfig.tabs[tabId]) {
// currentConfig.tabs[tabId].nodes.forEach(function(node) {
// if (deleted[node.id]) {
// console.log(" ",formatString(node.id),node.type,node.name || node.id);
// }
// })
// }
// });
// currentConfig.tabOrder.forEach(function(tabId) {
// if (deleted[tabId]) {
// console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")");
// }
// });
//
// currentDiff = {
// currentConfig: currentConfig,
// newConfig: newConfig,
// added: added,
// deleted: deleted,
// changed: changed,
// conflicted: conflicted
// }
// }
// function showDiff() {
// if (currentDiff) {
// var list = $("#node-dialog-view-diff-diff");
// list.editableList('empty');
// var currentConfig = currentDiff.currentConfig;
// currentConfig.tabOrder.forEach(function(tabId) {
// var tab = currentConfig.tabs[tabId];
// list.editableList('addItem',{tab:tab})
// });
// }
// $("#node-dialog-view-diff").dialog("open");
// }
function save(skipValidation,force) {
if (RED.nodes.dirty()) {
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
if (!force) {
if (!skipValidation) {
var hasUnknown = false;
var hasInvalid = false;
var hasUnusedConfig = false;
@ -196,6 +554,7 @@ RED.deploy = (function() {
$( "#node-dialog-confirm-deploy-config" ).hide();
$( "#node-dialog-confirm-deploy-unknown" ).hide();
$( "#node-dialog-confirm-deploy-unused" ).hide();
$( "#node-dialog-confirm-deploy-conflict" ).hide();
var showWarning = false;
@ -229,24 +588,28 @@ RED.deploy = (function() {
}
}
var nns = RED.nodes.createCompleteNodeSet();
$("#btn-deploy-icon").removeClass('fa-download');
$("#btn-deploy-icon").addClass('spinner');
RED.nodes.dirty(false);
var data = {flows:nns};
if (!force) {
data.rev = RED.nodes.version();
}
$.ajax({
url:"flows",
type: "POST",
data: JSON.stringify(nns),
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
headers: {
"Node-RED-Deployment-Type":deploymentType
}
}).done(function(data,textStatus,xhr) {
RED.nodes.dirty(false);
RED.nodes.version(data.rev);
if (hasUnusedConfig) {
RED.notify(
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
@ -264,10 +627,14 @@ RED.deploy = (function() {
}
});
RED.nodes.eachConfig(function (confNode) {
confNode.changed = false;
if (confNode.credentials) {
delete confNode.credentials;
}
});
RED.nodes.eachWorkspace(function(ws) {
ws.changed = false;
})
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
@ -276,6 +643,8 @@ RED.deploy = (function() {
RED.nodes.dirty(true);
if (xhr.status === 401) {
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
} else if (xhr.status === 409) {
resolveConflict(nns);
} else if (xhr.responseText) {
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
} else {
@ -287,7 +656,6 @@ RED.deploy = (function() {
});
}
}
return {
init: init
}

View File

@ -163,7 +163,11 @@ RED.editor = (function() {
function validateNodeEditorProperty(node,defaults,property,prefix) {
var input = $("#"+prefix+"-"+property);
if (input.length > 0) {
if (!validateNodeProperty(node, defaults, property,input.val())) {
var value = input.val();
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
value = input.text();
}
if (!validateNodeProperty(node, defaults, property,value)) {
input.addClass("input-error");
} else {
input.removeClass("input-error");
@ -295,17 +299,30 @@ RED.editor = (function() {
* @param node - the node being edited
* @param property - the name of the field
* @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
* @param definition - the definition of the field
*/
function preparePropertyEditor(node,property,prefix) {
function preparePropertyEditor(node,property,prefix,definition) {
var input = $("#"+prefix+"-"+property);
if (input.length === 0) {
return;
}
if (input.attr('type') === "checkbox") {
input.prop('checked',node[property]);
} else {
}
else {
var val = node[property];
if (val == null) {
val = "";
}
if (definition !== undefined && definition[property].hasOwnProperty("format") && definition[property].format !== "" && input[0].nodeName === "DIV") {
input.html(RED.text.format.getHtml(val, definition[property].format, {}, false, "en"));
RED.text.format.attach(input[0], definition[property].format, {}, false, "en");
} else {
input.val(val);
if (input[0].nodeName === 'INPUT' || input[0].nodeName === 'TEXTAREA') {
RED.text.bidi.prepareInput(input);
}
}
}
}
@ -317,12 +334,21 @@ RED.editor = (function() {
* @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
*/
function attachPropertyChangeHandler(node,definition,property,prefix) {
var input = $("#"+prefix+"-"+property);
if (definition !== undefined && "format" in definition[property] && definition[property].format !== "" && input[0].nodeName === "DIV") {
$("#"+prefix+"-"+property).on('change keyup', function(event,skipValidation) {
if (!skipValidation) {
validateNodeEditor(node,prefix);
}
});
} else {
$("#"+prefix+"-"+property).change(function(event,skipValidation) {
if (!skipValidation) {
validateNodeEditor(node,prefix);
}
});
}
}
/**
* Assign the value to each credential field
@ -345,7 +371,7 @@ RED.editor = (function() {
$('#' + prefix + '-' + cred).val('');
}
} else {
preparePropertyEditor(credData, cred, prefix);
preparePropertyEditor(credData, cred, prefix, credDef);
}
attachPropertyChangeHandler(node, credDef, cred, prefix);
}
@ -405,10 +431,10 @@ RED.editor = (function() {
}
} else {
console.log("Unknown type:", definition.defaults[d].type);
preparePropertyEditor(node,d,prefix);
preparePropertyEditor(node,d,prefix,definition.defaults);
}
} else {
preparePropertyEditor(node,d,prefix);
preparePropertyEditor(node,d,prefix,definition.defaults);
}
attachPropertyChangeHandler(node,definition.defaults,d,prefix);
}
@ -589,6 +615,8 @@ RED.editor = (function() {
var newValue;
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
@ -768,16 +796,17 @@ RED.editor = (function() {
} else {
ns = node_def.set.id;
}
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
if (!activeWorkspace) {
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
var configNodeScope = ""; // default to global
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
if (activeSubflow) {
configNodeScope = activeSubflow.id;
}
if (editing_config_node == null) {
editing_config_node = {
id: RED.nodes.id(),
_def: node_def,
type: type,
z: activeWorkspace.id,
z: configNodeScope,
users: []
}
for (var d in node_def.defaults) {
@ -1155,7 +1184,7 @@ RED.editor = (function() {
}
configNodes.forEach(function(cn) {
select.append('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'>'+cn.__label__+'</option>');
select.append('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'>'+RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)+'</option>');
delete cn.__label__;
});
@ -1197,7 +1226,6 @@ RED.editor = (function() {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(newName);
}
var newDescription = subflowEditor.getValue();
@ -1290,6 +1318,7 @@ RED.editor = (function() {
});
$("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name"));
subflowEditor.getSession().setValue(subflow.info||"",-1);
var userCount = 0;
var subflowType = "subflow:"+editing_node.id;
@ -1363,7 +1392,7 @@ RED.editor = (function() {
if (options.globals) {
setTimeout(function() {
if (!!session.$worker) {
session.$worker.send("setOptions", [{globals: options.globals}]);
session.$worker.send("setOptions", [{globals: options.globals, esversion:6}]);
}
},100);
}

View File

@ -124,6 +124,7 @@ RED.keyboard = (function() {
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">.</span></td><td>'+RED._("keyboard.searchBox")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td rowspan="2">'+RED._("keyboard.deleteSelected")+'</td></tr>'+
'<tr><td><span class="help-key">Backspace</span></td></tr>'+

View File

@ -0,0 +1,749 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.palette.editor = (function() {
var editorTabs;
var filterInput;
var searchInput;
var nodeList;
var packageList;
var loadedList = [];
var filteredList = [];
var typesInUse = {};
var nodeEntries = {};
var eventTimers = {};
var activeFilter = "";
function delayCallback(start,callback) {
var delta = Date.now() - start;
if (delta < 300) {
delta = 300;
} else {
delta = 0;
}
setTimeout(function() {
callback();
},delta);
}
function changeNodeState(id,state,shade,callback) {
shade.show();
var start = Date.now();
$.ajax({
url:"nodes/"+id,
type: "PUT",
data: JSON.stringify({
enabled: state
}),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
delayCallback(start,function() {
shade.hide();
callback();
});
}).fail(function(xhr,textStatus,err) {
delayCallback(start,function() {
shade.hide();
callback(xhr);
});
})
}
function installNodeModule(id,shade,callback) {
shade.show();
$.ajax({
url:"nodes",
type: "POST",
data: JSON.stringify({
module: id
}),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
shade.hide();
callback();
}).fail(function(xhr,textStatus,err) {
shade.hide();
callback(xhr);
});
}
function removeNodeModule(id,callback) {
$.ajax({
url:"nodes/"+id,
type: "DELETE"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
})
}
function refreshNodeModule(module) {
if (!eventTimers.hasOwnProperty(module)) {
eventTimers[module] = setTimeout(function() {
delete eventTimers[module];
_refreshNodeModule(module);
},100);
}
}
function getContrastingBorder(rgbColor){
var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
if (parts) {
var r = parseInt(parts[1]);
var g = parseInt(parts[2]);
var b = parseInt(parts[3]);
var yiq = ((r*299)+(g*587)+(b*114))/1000;
if (yiq > 160) {
r = Math.floor(r*0.8);
g = Math.floor(g*0.8);
b = Math.floor(b*0.8);
return "rgb("+r+","+g+","+b+")";
}
}
return rgbColor;
}
function formatUpdatedAt(dateString) {
var now = new Date();
var d = new Date(dateString);
var delta = (Date.now() - new Date(dateString).getTime())/1000;
if (delta < 60) {
return RED._('palette.editor.times.seconds');
}
delta = Math.floor(delta/60);
if (delta < 10) {
return RED._('palette.editor.times.minutes');
}
if (delta < 60) {
return RED._('palette.editor.times.minutesV',{count:delta});
}
delta = Math.floor(delta/60);
if (delta < 24) {
return RED._('palette.editor.times.hoursV',{count:delta});
}
delta = Math.floor(delta/24);
if (delta < 7) {
return RED._('palette.editor.times.daysV',{count:delta})
}
var weeks = Math.floor(delta/7);
var days = delta%7;
if (weeks < 4) {
return RED._('palette.editor.times.weeksV',{count:weeks})
}
var months = Math.floor(weeks/4);
weeks = weeks%4;
if (months < 12) {
return RED._('palette.editor.times.monthsV',{count:months})
}
var years = Math.floor(months/12);
months = months%12;
if (months === 0) {
return RED._('palette.editor.times.yearsV',{count:years})
} else {
return RED._('palette.editor.times.year'+(years>1?'s':'')+'MonthsV',{y:years,count:months})
}
}
function _refreshNodeModule(module) {
if (!nodeEntries.hasOwnProperty(module)) {
nodeEntries[module] = {info:RED.nodes.registry.getModule(module)};
var index = [module];
for (var s in nodeEntries[module].info.sets) {
if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
index.push(s);
index = index.concat(nodeEntries[module].info.sets[s].types)
}
}
nodeEntries[module].index = index.join(",").toLowerCase();
nodeList.editableList('addItem', nodeEntries[module]);
//console.log(nodeList.editableList('items'));
} else {
var moduleInfo = nodeEntries[module].info;
var nodeEntry = nodeEntries[module].elements;
if (nodeEntry) {
var activeTypeCount = 0;
var typeCount = 0;
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
for (var setName in moduleInfo.sets) {
if (moduleInfo.sets.hasOwnProperty(setName)) {
var inUseCount = 0;
var set = moduleInfo.sets[setName];
var setElements = nodeEntry.sets[setName];
if (set.enabled) {
activeTypeCount += set.types.length;
}
typeCount += set.types.length;
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
var t = moduleInfo.sets[setName].types[i];
inUseCount += (typesInUse[t]||0);
var swatch = setElements.swatches[t];
if (set.enabled) {
var def = RED.nodes.getType(t);
if (def && def.color) {
swatch.css({background:def.color});
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
} else {
swatch.css({background:"#eee",border:"1px dashed #999"})
}
} else {
swatch.css({background:"#eee",border:"1px dashed #999"})
}
}
nodeEntries[module].setUseCount[setName] = inUseCount;
nodeEntries[module].totalUseCount += inUseCount;
if (inUseCount > 0) {
setElements.enableButton.html(RED._('palette.editor.inuse'));
setElements.enableButton.addClass('disabled');
} else {
setElements.enableButton.removeClass('disabled');
if (set.enabled) {
setElements.enableButton.html(RED._('palette.editor.disable'));
} else {
setElements.enableButton.html(RED._('palette.editor.enable'));
}
}
setElements.setRow.toggleClass("palette-module-set-disabled",!set.enabled);
}
}
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
nodeEntry.setCount.html(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
if (nodeEntries[module].totalUseCount > 0) {
nodeEntry.enableButton.html(RED._('palette.editor.inuse'));
nodeEntry.enableButton.addClass('disabled');
nodeEntry.removeButton.hide();
} else {
nodeEntry.enableButton.removeClass('disabled');
nodeEntry.removeButton.show();
if (activeTypeCount === 0) {
nodeEntry.enableButton.html(RED._('palette.editor.enableall'));
} else {
nodeEntry.enableButton.html(RED._('palette.editor.disableall'));
}
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
}
}
}
}
function showPaletteEditor() {
if (RED.settings.theme('palette.editable') === false) {
return;
}
$("#header-shade").show();
$("#editor-shade").show();
$("#sidebar-shade").show();
$("#sidebar-separator").hide();
$("#main-container").addClass("palette-expanded");
setTimeout(function() {
editorTabs.resize();
},250);
RED.events.emit("palette-editor:open");
}
function hidePaletteEditor() {
$("#main-container").removeClass("palette-expanded");
$("#header-shade").hide();
$("#editor-shade").hide();
$("#sidebar-shade").hide();
$("#sidebar-separator").show();
$("#palette-editor").find('.expanded').each(function(i,el) {
$(el).find(".palette-module-content").slideUp();
$(el).removeClass('expanded');
});
filterInput.searchBox('value',"");
searchInput.searchBox('value',"");
RED.events.emit("palette-editor:close");
}
function filterChange(val) {
activeFilter = val.toLowerCase();
var visible = nodeList.editableList('filter');
var size = nodeList.editableList('length');
if (val === "") {
filterInput.searchBox('count');
} else {
filterInput.searchBox('count',visible+" / "+size);
}
}
var catalogueCount;
var catalogueLoadStatus = [];
var catalogueLoadStart;
var activeSort = sortModulesAZ;
function handleCatalogResponse(catalog,index,v) {
catalogueLoadStatus.push(v);
if (v.modules) {
v.modules.forEach(function(m) {
m.index = [m.id];
if (m.keywords) {
m.index = m.index.concat(m.keywords);
}
if (m.updated_at) {
m.timestamp = new Date(m.updated_at).getTime();
} else {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
})
loadedList = loadedList.concat(v.modules);
}
searchInput.searchBox('count',loadedList.length);
if (catalogueCount > 1) {
$(".palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>"+catalogueLoadStatus.length+"/"+catalogueCount);
}
if (catalogueLoadStatus.length === catalogueCount) {
var delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() {
$("#palette-module-install-shade").hide();
},Math.max(delta,0));
}
}
function initInstallTab() {
if (loadedList.length === 0) {
loadedList = [];
packageList.editableList('empty');
$(".palette-module-shade-status").html(RED._('palette.editor.loading'));
var catalogues = RED.settings.theme('palette.catalogues')||['http://catalogue.nodered.org/catalogue.json'];
catalogueLoadStatus = [];
catalogueCount = catalogues.length;
if (catalogues.length > 1) {
$(".palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>0/"+catalogues.length);
}
$("#palette-module-install-shade").show();
catalogueLoadStart = Date.now();
catalogues.forEach(function(catalog,index) {
$.getJSON(catalog, {_: new Date().getTime()},function(v) {
handleCatalogResponse(catalog,index,v);
})
});
}
}
function refreshFilteredItems() {
packageList.editableList('empty');
filteredList.sort(activeSort);
for (var i=0;i<Math.min(10,filteredList.length);i++) {
packageList.editableList('addItem',filteredList[i]);
}
if (filteredList.length === 0) {
packageList.editableList('addItem',{});
}
if (filteredList.length > 10) {
packageList.editableList('addItem',{start:10,more:filteredList.length-10})
}
}
function sortModulesAZ(A,B) {
return A.info.id.localeCompare(B.info.id);
}
function sortModulesRecent(A,B) {
return -1 * (A.info.timestamp-B.info.timestamp);
}
function init() {
if (RED.settings.theme('palette.editable') === false) {
return;
}
editorTabs = RED.tabs.create({
id:"palette-editor-tabs",
onchange:function(tab) {
$("#palette-editor .palette-editor-tab").hide();
tab.content.show();
if (filterInput) {
filterInput.searchBox('value',"");
}
if (searchInput) {
searchInput.searchBox('value',"");
}
if (tab.id === 'install') {
initInstallTab();
if (searchInput) {
searchInput.focus();
}
} else {
if (filterInput) {
filterInput.focus();
}
}
},
minimumActiveTabWidth: 110
});
$("#editor-shade").click(function() {
if ($("#main-container").hasClass("palette-expanded")) {
hidePaletteEditor();
}
});
$("#palette-editor-close").on("click", function(e) {
hidePaletteEditor();
})
var modulesTab = $('<div>',{class:"palette-editor-tab"}).appendTo("#palette-editor");
editorTabs.addTab({
id: 'nodes',
label: RED._('palette.editor.tab-nodes'),
content: modulesTab
})
var filterDiv = $('<div>',{class:"palette-search"}).appendTo(modulesTab);
filterInput = $('<input type="text" data-i18n="[placeholder]palette.filter"></input>')
.appendTo(filterDiv)
.searchBox({
delay: 200,
change: function() {
filterChange($(this).val());
}
});
nodeList = $('<ol>',{id:"palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({
addButton: false,
scrollOnAdd: false,
sort: function(A,B) {
return A.info.name.localeCompare(B.info.name);
},
filter: function(data) {
if (activeFilter === "" ) {
return true;
}
return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
},
addItem: function(container,i,object) {
var entry = object.info;
if (entry) {
var headerRow = $('<div>',{class:"palette-module-header"}).appendTo(container);
var titleRow = $('<div class="palette-module-meta palette-module-name"><i class="fa fa-cube"></i></div>').appendTo(headerRow);
$('<span>').html(entry.name).appendTo(titleRow);
var metaRow = $('<div class="palette-module-meta palette-module-version"><i class="fa fa-tag"></i></div>').appendTo(headerRow);
$('<span>').html(entry.version).appendTo(metaRow);
var buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow);
var setButton = $('<a href="#" class="editor-button editor-button-small palette-module-set-button"><i class="fa fa-angle-right palette-module-node-chevron"></i> </a>').appendTo(buttonRow);
var setCount = $('<span>').appendTo(setButton);
var buttonGroup = $('<div>',{class:"palette-module-button-group"}).appendTo(buttonRow);
var removeButton = $('<a href="#" class="editor-button editor-button-small"></a>').html(RED._('palette.editor.remove')).appendTo(buttonGroup);
removeButton.click(function(evt) {
evt.preventDefault();
shade.show();
removeNodeModule(entry.name, function(xhr) {
console.log(xhr);
})
})
if (!entry.local) {
removeButton.hide();
}
var enableButton = $('<a href="#" class="editor-button editor-button-small"></a>').html(RED._('palette.editor.disableall')).appendTo(buttonGroup);
var contentRow = $('<div>',{class:"palette-module-content"}).appendTo(container);
var shade = $('<div class="palette-module-shade hide"><img src="red/images/spin.svg" class="palette-spinner"/></div>').appendTo(container);
object.elements = {
removeButton: removeButton,
enableButton: enableButton,
setCount: setCount,
container: container,
shade: shade,
sets: {}
}
setButton.click(function(evt) {
evt.preventDefault();
if (container.hasClass('expanded')) {
container.removeClass('expanded');
contentRow.slideUp();
} else {
container.addClass('expanded');
contentRow.slideDown();
}
})
var setList = Object.keys(entry.sets)
setList.sort(function(A,B) {
return A.toLowerCase().localeCompare(B.toLowerCase());
});
setList.forEach(function(setName) {
var set = entry.sets[setName];
var setRow = $('<div>',{class:"palette-module-set"}).appendTo(contentRow);
var buttonGroup = $('<div>',{class:"palette-module-set-button-group"}).appendTo(setRow);
var typeSwatches = {};
set.types.forEach(function(t) {
var typeDiv = $('<div>',{class:"palette-module-type"}).appendTo(setRow);
typeSwatches[t] = $('<span>',{class:"palette-module-type-swatch"}).appendTo(typeDiv);
$('<span>',{class:"palette-module-type-node"}).html(t).appendTo(typeDiv);
})
var enableButton = $('<a href="#" class="editor-button editor-button-small"></a>').appendTo(buttonGroup);
enableButton.click(function(evt) {
evt.preventDefault();
if (object.setUseCount[setName] === 0) {
var currentSet = RED.nodes.registry.getNodeSet(set.id);
shade.show();
changeNodeState(set.id,!currentSet.enabled,shade,function(xhr){
console.log(xhr)
});
}
})
object.elements.sets[set.name] = {
setRow: setRow,
enableButton: enableButton,
swatches: typeSwatches
};
});
enableButton.click(function(evt) {
evt.preventDefault();
if (object.totalUseCount === 0) {
changeNodeState(entry.name,(container.hasClass('disabled')),shade,function(xhr){
console.log(xhr)
});
}
})
refreshNodeModule(entry.name);
} else {
$('<div>',{class:"red-ui-search-empty"}).html(RED._('search.empty')).appendTo(container);
}
}
});
var installTab = $('<div>',{class:"palette-editor-tab hide"}).appendTo("#palette-editor");
editorTabs.addTab({
id: 'install',
label: RED._('palette.editor.tab-install'),
content: installTab
})
var toolBar = $('<div>',{class:"palette-editor-toolbar"}).appendTo(installTab);
var searchDiv = $('<div>',{class:"palette-search"}).appendTo(installTab);
searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
.appendTo(searchDiv)
.searchBox({
delay: 300,
change: function() {
var searchTerm = $(this).val();
if (searchTerm.length > 0) {
filteredList = loadedList.filter(function(m) {
return (m.index.indexOf(searchTerm) > -1);
}).map(function(f) { return {info:f}});
refreshFilteredItems();
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
} else {
searchInput.searchBox('count',loadedList.length);
packageList.editableList('empty');
}
}
});
$('<span>').html(RED._("palette.editor.sort")+' ').appendTo(toolBar);
var sortGroup = $('<span class="button-group"></span> ').appendTo(toolBar);
var sortAZ = $('<a href="#" class="sidebar-header-button-toggle selected" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup);
var sortRecent = $('<a href="#" class="sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup);
sortAZ.click(function(e) {
e.preventDefault();
if ($(this).hasClass("selected")) {
return;
}
$(this).addClass("selected");
sortRecent.removeClass("selected");
activeSort = sortModulesAZ;
refreshFilteredItems();
});
sortRecent.click(function(e) {
e.preventDefault();
if ($(this).hasClass("selected")) {
return;
}
$(this).addClass("selected");
sortAZ.removeClass("selected");
activeSort = sortModulesRecent;
refreshFilteredItems();
});
var refreshSpan = $('<span>').appendTo(toolBar);
var refreshButton = $('<a href="#" class="sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
refreshButton.click(function(e) {
e.preventDefault();
loadedList = [];
initInstallTab();
})
packageList = $('<ol>',{style:"position: absolute;top: 78px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(container,i,object) {
if (object.more) {
container.addClass('palette-module-more');
var moreRow = $('<div>',{class:"palette-module-header palette-module"}).appendTo(container);
var moreLink = $('<a href="#"></a>').html(RED._('palette.editor.more',{count:object.more})).appendTo(moreRow);
moreLink.click(function(e) {
e.preventDefault();
packageList.editableList('removeItem',object);
for (var i=object.start;i<Math.min(object.start+10,object.start+object.more);i++) {
packageList.editableList('addItem',filteredList[i]);
}
if (object.more > 10) {
packageList.editableList('addItem',{start:object.start+10, more:object.more-10})
}
})
return;
}
if (object.info) {
var entry = object.info;
var headerRow = $('<div>',{class:"palette-module-header"}).appendTo(container);
var titleRow = $('<div class="palette-module-meta"><i class="fa fa-cube"></i></div>').appendTo(headerRow);
$('<span>',{class:"palette-module-name"}).html(entry.name||entry.id).appendTo(titleRow);
$('<a target="_blank" class="palette-module-link"><i class="fa fa-external-link"></i></a>').attr('href',entry.url).appendTo(titleRow);
var descRow = $('<div class="palette-module-meta"></div>').appendTo(headerRow);
$('<div>',{class:"palette-module-description"}).html(entry.description).appendTo(descRow);
var metaRow = $('<div class="palette-module-meta"></div>').appendTo(headerRow);
$('<span class="palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
$('<span class="palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
var buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow);
var buttonGroup = $('<div>',{class:"palette-module-button-group"}).appendTo(buttonRow);
var shade = $('<div class="palette-module-shade hide"><img src="red/images/spin.svg" class="palette-spinner"/></div>').appendTo(container);
var installButton = $('<a href="#" class="editor-button editor-button-small"></a>').html(RED._('palette.editor.install')).appendTo(buttonGroup);
installButton.click(function(e) {
e.preventDefault();
if (!$(this).hasClass('disabled')) {
installNodeModule(entry.id,shade,function(xhr) {
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}));
}
}
})
}
})
if (nodeEntries.hasOwnProperty(entry.id)) {
installButton.addClass('disabled');
installButton.html(RED._('palette.editor.installed'));
}
object.elements = {
installButton:installButton
}
} else {
$('<div>',{class:"red-ui-search-empty"}).html(RED._('search.empty')).appendTo(container);
}
}
});
$('<div id="palette-module-install-shade" class="palette-module-shade hide"><div class="palette-module-shade-status"></div><img src="red/images/spin.svg" class="palette-spinner"/></div>').appendTo(installTab);
RED.events.on('registry:node-set-enabled', function(ns) {
refreshNodeModule(ns.module);
});
RED.events.on('registry:node-set-disabled', function(ns) {
refreshNodeModule(ns.module);
});
RED.events.on('registry:node-type-added', function(nodeType) {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
}
});
RED.events.on('registry:node-type-removed', function(nodeType) {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
}
});
RED.events.on('registry:node-set-added', function(ns) {
refreshNodeModule(ns.module);
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === ns.module) {
filteredList[i].elements.installButton.hide();
break;
}
}
});
RED.events.on('registry:node-set-removed', function(ns) {
var module = RED.nodes.registry.getModule(ns.module);
if (!module) {
var entry = nodeEntries[ns.module];
if (entry) {
nodeList.editableList('removeItem', entry);
delete nodeEntries[ns.module];
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === ns.module) {
filteredList[i].elements.installButton.show();
break;
}
}
}
}
});
RED.events.on('nodes:add', function(n) {
if (!/^subflow:/.test(n.type)) {
typesInUse[n.type] = (typesInUse[n.type]||0)+1;
if (typesInUse[n.type] === 1) {
var ns = RED.nodes.registry.getNodeSetForType(n.type);
refreshNodeModule(ns.module);
}
}
})
RED.events.on('nodes:remove', function(n) {
if (typesInUse.hasOwnProperty(n.type)) {
typesInUse[n.type]--;
if (typesInUse[n.type] === 0) {
delete typesInUse[n.type];
var ns = RED.nodes.registry.getNodeSetForType(n.type);
refreshNodeModule(ns.module);
}
}
})
}
return {
init: init,
show: showPaletteEditor
}
})();

View File

@ -17,7 +17,7 @@
RED.palette = (function() {
var exclusion = ['config','unknown','deprecated'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
var categoryContainers = {};
@ -91,15 +91,15 @@ RED.palette = (function() {
el.css({height:multiLineNodeHeight+"px"});
var labelElement = el.find(".palette_label");
labelElement.html(lines);
labelElement.html(lines).attr('dir', RED.text.bidi.resolveBaseTextDir(lines));
el.find(".palette_port").css({top:(multiLineNodeHeight/2-5)+"px"});
var popOverContent;
try {
var l = "<p><b>"+label+"</b></p>";
var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>";
if (label != type) {
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
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) {
@ -174,7 +174,7 @@ RED.palette = (function() {
}
if ($("#palette-base-category-"+rootCategory).length === 0) {
if(core.indexOf(rootCategory) !== -1){
if(coreCategories.indexOf(rootCategory) !== -1){
createCategoryContainer(rootCategory, RED._("node-red:palette.label."+rootCategory, {defaultValue:rootCategory}));
} else {
var ns = def.set.id;
@ -363,14 +363,7 @@ RED.palette = (function() {
});
}
function filterChange() {
var val = $("#palette-search-input").val();
if (val === "") {
$("#palette-search-clear").hide();
} else {
$("#palette-search-clear").show();
}
function filterChange(val) {
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
$("#palette-container .palette_node").each(function(i,el) {
var currentLabel = $(el).find(".palette_label").text();
@ -395,33 +388,69 @@ RED.palette = (function() {
}
function init() {
$(".palette-spinner").show();
if (RED.settings.paletteCategories) {
RED.settings.paletteCategories.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
} else {
core.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
RED.events.on('registry:node-type-added', function(nodeType) {
var def = RED.nodes.getType(nodeType);
addNodeType(nodeType,def);
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
$("#palette-search-clear").on("click",function(e) {
e.preventDefault();
$("#palette-search-input").val("");
filterChange();
$("#palette-search-input").focus();
});
RED.events.on('registry:node-type-removed', function(nodeType) {
removeNodeType(nodeType);
});
$("#palette-search-input").val("");
$("#palette-search-input").on("keyup",function() {
filterChange();
RED.events.on('registry:node-set-enabled', function(nodeSet) {
for (var j=0;j<nodeSet.types.length;j++) {
showNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]);
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
}
});
RED.events.on('registry:node-set-disabled', function(nodeSet) {
for (var j=0;j<nodeSet.types.length;j++) {
hideNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]);
if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def);
}
}
});
RED.events.on('registry:node-set-removed', function(nodeSet) {
if (nodeSet.added) {
for (var j=0;j<nodeSet.types.length;j++) {
removeNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]);
if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def);
}
}
}
});
$("#palette-search-input").on("focus",function() {
$("body").one("mousedown",function() {
$("#palette-search-input").blur();
});
$("#palette > .palette-spinner").show();
$("#palette-search input").searchBox({
delay: 100,
change: function() {
filterChange($(this).val());
}
})
var categoryList = coreCategories;
if (RED.settings.paletteCategories) {
categoryList = RED.settings.paletteCategories;
} else if (RED.settings.theme('palette.categories')) {
categoryList = RED.settings.theme('palette.categories');
}
if (!Array.isArray(categoryList)) {
categoryList = coreCategories
}
categoryList.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
$("#palette-collapse-all").on("click", function(e) {

295
editor/js/ui/search.js Normal file
View File

@ -0,0 +1,295 @@
/**
* Copyright 2013, 2016 IBM Corp.
*
* 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.search = (function() {
var disabled = false;
var dialog = null;
var searchInput;
var searchResults;
var selected = -1;
var visible = false;
var index = {};
var keys = [];
var results = [];
function indexNode(n) {
var l = "";
if (n._def && n._def.label) {
l = n._def.label;
try {
l = (typeof l === "function" ? l.call(n) : l);
if (l) {
l = (""+l).toLowerCase();
index[l] = index[l] || {};
index[l][n.id] = {node:n,label:l}
}
} catch(err) {
console.log("Definition error: "+n.type+".label",err);
}
}
l = l||n.label||n.name||n.id||"";
var properties = ['id','type','name','label','info'];
for (var i=0;i<properties.length;i++) {
if (n.hasOwnProperty(properties[i])) {
var v = n[properties[i]];
if (typeof v === 'string' || typeof v === 'number') {
v = (""+v).toLowerCase();
index[v] = index[v] || {};
index[v][n.id] = {node:n,label:l};
}
}
}
}
function indexWorkspace() {
index = {};
RED.nodes.eachWorkspace(indexNode);
RED.nodes.eachSubflow(indexNode);
RED.nodes.eachConfig(indexNode);
RED.nodes.eachNode(indexNode);
keys = Object.keys(index);
keys.sort();
keys.forEach(function(key) {
index[key] = Object.keys(index[key]).map(function(id) {
return index[key][id];
})
})
}
function search(val) {
searchResults.editableList('empty');
selected = -1;
results = [];
if (val.length > 0) {
val = val.toLowerCase();
var i;
var j;
var list = [];
var nodes = {};
for (i=0;i<keys.length;i++) {
var key = keys[i];
var kpos = keys[i].indexOf(val);
if (kpos > -1) {
for (j=0;j<index[key].length;j++) {
var node = index[key][j];
nodes[node.node.id] = nodes[node.node.id] = node;
nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos);
}
}
}
list = Object.keys(nodes);
list.sort(function(A,B) {
return nodes[A].index - nodes[B].index;
});
for (i=0;i<list.length;i++) {
results.push(nodes[list[i]]);
}
if (results.length > 0) {
for (i=0;i<Math.min(results.length,25);i++) {
searchResults.editableList('addItem',results[i])
}
} else {
searchResults.editableList('addItem',{});
}
}
}
function ensureSelectedIsVisible() {
var selectedEntry = searchResults.find("li.selected");
if (selectedEntry.length === 1) {
var scrollWindow = searchResults.parent();
var scrollHeight = scrollWindow.height();
var scrollOffset = scrollWindow.scrollTop();
var y = selectedEntry.position().top;
var h = selectedEntry.height();
if (y+h > scrollHeight) {
scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-10)},50);
} else if (y<0) {
scrollWindow.animate({scrollTop: '+='+(y-10)},50);
}
}
}
function createDialog() {
dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#main-container");
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
searchInput = $('<input type="text" placeholder="search your flows">').appendTo(searchDiv).searchBox({
delay: 200,
change: function() {
search($(this).val());
}
});
searchInput.on('keydown',function(evt) {
var children;
if (results.length > 0) {
if (evt.keyCode === 40) {
// Down
children = searchResults.children();
if (selected < children.length-1) {
if (selected > -1) {
$(children[selected]).removeClass('selected');
}
selected++;
}
$(children[selected]).addClass('selected');
ensureSelectedIsVisible();
evt.preventDefault();
} else if (evt.keyCode === 38) {
// Up
children = searchResults.children();
if (selected > 0) {
if (selected < children.length) {
$(children[selected]).removeClass('selected');
}
selected--;
}
$(children[selected]).addClass('selected');
ensureSelectedIsVisible();
evt.preventDefault();
} else if (evt.keyCode === 13) {
// Enter
if (results.length > 0) {
reveal(results[Math.max(0,selected)].node);
}
}
}
});
var searchResultsDiv = $("<div>",{class:"red-ui-search-results-container"}).appendTo(dialog);
searchResults = $('<ol>',{id:"search-result-list", style:"position: absolute;top: 5px;bottom: 5px;left: 5px;right: 5px;"}).appendTo(searchResultsDiv).editableList({
addButton: false,
addItem: function(container,i,object) {
var node = object.node;
if (node === undefined) {
$('<div>',{class:"red-ui-search-empty"}).html(RED._('search.empty')).appendTo(container);
} else {
var def = node._def;
var div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
var colour = def.color;
var icon_url = "arrow-in.png";
if (node.type === 'tab') {
colour = "#C0DEED";
icon_url = "subflow.png";
} else if (def.category === 'config') {
icon_url = "cog.png";
} else if (node.type === 'unknown') {
icon_url = "alert.png";
} else {
try {
icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
} catch(err) {
console.log("Definition error: "+nt+".icon",err);
}
}
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
if (node.z) {
var workspace = RED.nodes.workspace(node.z);
if (!workspace) {
workspace = RED.nodes.subflow(node.z);
workspace = "subflow:"+workspace.name;
} else {
workspace = "flow:"+workspace.label;
}
$('<div>',{class:"red-ui-search-result-node-flow"}).html(workspace).appendTo(contentDiv);
}
$('<div>',{class:"red-ui-search-result-node-label"}).html(object.label || node.id).appendTo(contentDiv);
$('<div>',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv);
$('<div>',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv);
div.click(function(evt) {
evt.preventDefault();
reveal(node);
});
}
},
scrollOnAdd: false
});
}
function reveal(node) {
hide();
RED.view.reveal(node.id);
}
function show() {
if (!visible) {
RED.keyboard.add("*",/* ESCAPE */ 27,function(){hide();d3.event.preventDefault();});
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$("#sidebar-shade").show();
$("#sidebar-separator").hide();
indexWorkspace();
if (dialog === null) {
createDialog();
}
dialog.slideDown();
visible = true;
}
searchInput.focus();
}
function hide() {
if (visible) {
RED.keyboard.remove(/* ESCAPE */ 27);
visible = false;
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$("#sidebar-shade").hide();
$("#sidebar-separator").show();
if (dialog !== null) {
dialog.slideUp(200,function() {
searchInput.searchBox('value','');
});
}
}
}
function init() {
RED.keyboard.add("*",/* . */ 190,{ctrl:true},function(){if (!disabled) { show(); } d3.event.preventDefault();});
RED.events.on("editor:open",function() { disabled = true; });
RED.events.on("editor:close",function() { disabled = false; });
RED.events.on("palette-editor:open",function() { disabled = true; });
RED.events.on("palette-editor:close",function() { disabled = false; });
$("#header-shade").on('mousedown',hide);
$("#editor-shade").on('mousedown',hide);
$("#palette-shade").on('mousedown',hide);
$("#sidebar-shade").on('mousedown',hide);
}
return {
init: init,
show: show,
hide: hide
};
})();

View File

@ -149,7 +149,7 @@ RED.sidebar.config = (function() {
currentType = node.type;
}
var entry = $('<li class="palette_node config_node"></li>').appendTo(list);
var entry = $('<li class="palette_node config_node palette_node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
$('<div class="palette_label"></div>').text(label).appendTo(entry);
var iconContainer = $('<div/>',{class:"palette_icon_container palette_icon_container_right"}).text(node.users.length).appendTo(entry);
@ -280,8 +280,8 @@ RED.sidebar.config = (function() {
}
function show(unused) {
if (unused !== undefined) {
function show(id) {
if (typeof id === 'boolean') {
if (unused) {
$('#workspace-config-node-filter-unused').click();
} else {
@ -289,6 +289,36 @@ RED.sidebar.config = (function() {
}
}
refreshConfigNodeList();
if (typeof id === "string") {
$('#workspace-config-node-filter-all').click();
id = id.replace(/\./g,"-");
setTimeout(function() {
var node = $(".palette_node_id_"+id);
var y = node.position().top;
var h = node.height();
var scrollWindow = $(".sidebar-node-config");
var scrollHeight = scrollWindow.height();
if (y+h > scrollHeight) {
scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-30)},150);
} else if (y<0) {
scrollWindow.animate({scrollTop: '+='+(y-10)},150);
}
var flash = 21;
var flashFunc = function() {
if ((flash%2)===0) {
node.removeClass('node_highlighted');
} else {
node.addClass('node_highlighted');
}
flash--;
if (flash >= 0) {
setTimeout(flashFunc,100);
}
}
flashFunc();
},100);
}
RED.sidebar.show("config");
}
return {

View File

@ -70,7 +70,7 @@ RED.sidebar.info = (function() {
var table = '<table class="node-info"><tbody>';
table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.node")+'</td></tr>';
if (node.type != "subflow" && node.name) {
table += "<tr><td>"+RED._("common.label.name")+"</td><td>&nbsp;"+node.name+"</td></tr>";
table += '<tr><td>'+RED._("common.label.name")+'</td><td>&nbsp;<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'">'+node.name+'</span></td></tr>';
}
table += "<tr><td>"+RED._("sidebar.info.type")+"</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>"+RED._("sidebar.info.id")+"</td><td>&nbsp;"+node.id+"</td></tr>";
@ -93,7 +93,7 @@ RED.sidebar.info = (function() {
userCount++;
}
});
table += "<tr><td>"+RED._("common.label.name")+"</td><td>"+subflowNode.name+"</td></tr>";
table += '<tr><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>';
table += "<tr><td>"+RED._("sidebar.info.instances")+"</td><td>"+userCount+"</td></tr>";
}
@ -140,13 +140,14 @@ RED.sidebar.info = (function() {
table += "</tbody></table><hr/>";
if (!subflowNode && node.type != "comment") {
var helpText = $("script[data-help-name$='"+node.type+"']").html()||"";
table += '<div class="node-help">'+helpText+"</div>";
table += '<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(helpText)+'">'+helpText+'</span></div>';
}
if (subflowNode) {
table += '<div class="node-help">'+marked(subflowNode.info||"")+'</div>';
table += '<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.info||"")+'">'+marked(subflowNode.info||"")+'</span></div>';
} else if (node._def && node._def.info) {
var info = node._def.info;
table += '<div class="node-help">'+marked(typeof info === "function" ? info.call(node) : info)+'</div>';
var textInfo = (typeof info === "function" ? info.call(node) : info);
table += '<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(textInfo)+'">'+marked(textInfo)+'</span></div>';
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
}

View File

@ -110,6 +110,7 @@ RED.tray = (function() {
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$(".sidebar-shade").show();
tray.preferredWidth = Math.max(el.width(),500);
@ -259,6 +260,7 @@ RED.tray = (function() {
if (stack.length === 0) {
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$(".sidebar-shade").hide();
RED.events.emit("editor:close");
RED.view.focus();

View File

@ -642,6 +642,8 @@ RED.view = (function() {
mousePos = mouse_position;
var minX = 0;
var minY = 0;
var maxX = space_width;
var maxY = space_height;
for (var n = 0; n<moving_set.length; n++) {
node = moving_set[n];
if (d3.event.shiftKey) {
@ -653,6 +655,8 @@ RED.view = (function() {
node.n.dirty = true;
minX = Math.min(node.n.x-node.n.w/2-5,minX);
minY = Math.min(node.n.y-node.n.h/2-5,minY);
maxX = Math.max(node.n.x+node.n.w/2+5,maxX);
maxY = Math.max(node.n.y+node.n.h/2+5,maxY);
}
if (minX !== 0 || minY !== 0) {
for (i = 0; i<moving_set.length; i++) {
@ -661,6 +665,13 @@ RED.view = (function() {
node.n.y -= minY;
}
}
if (maxX !== space_width || maxY !== space_height) {
for (i = 0; i<moving_set.length; i++) {
node = moving_set[i];
node.n.x -= (maxX - space_width);
node.n.y -= (maxY - space_height);
}
}
if (snapGrid != d3.event.shiftKey && moving_set.length > 0) {
var gridOffset = [0,0];
node = moving_set[0];
@ -796,7 +807,9 @@ RED.view = (function() {
if (moving_set.length > 0) {
var ns = [];
for (var j=0;j<moving_set.length;j++) {
ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy});
ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy,changed:moving_set[j].n.changed});
moving_set[j].n.dirty = true;
moving_set[j].n.changed = true;
}
historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()};
if (activeSpliceLink) {
@ -977,10 +990,13 @@ RED.view = (function() {
if (moving_set.length > 0) {
var ns = [];
for (var i=0;i<moving_set.length;i++) {
ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy});
ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy,changed:moving_set[i].n.changed});
moving_set[i].n.changed = true;
moving_set[i].n.dirty = true;
delete moving_set[i].ox;
delete moving_set[i].oy;
}
redraw();
RED.history.push({t:"move",nodes:ns,dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
}
@ -1379,7 +1395,7 @@ RED.view = (function() {
options.push({name:"delete",disabled:(moving_set.length===0 && selected_link === null),onselect:function() {deleteSelection();}});
options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}});
options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}});
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,true);}});
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,false,true);}});
options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}});
options.push({name:"select",onselect:function() {selectAll();}});
options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
@ -1793,6 +1809,7 @@ RED.view = (function() {
l = d._def.label;
try {
l = (typeof l === "function" ? l.call(d) : l)||"";
l = RED.text.bidi.enforceTextDirectionWithUCC(l);
} catch(err) {
console.log("Definition error: "+d.type+".label",err);
l = d.type;
@ -2155,7 +2172,6 @@ RED.view = (function() {
).classed("link_selected", false);
}
if (d3.event) {
d3.event.preventDefault();
}
@ -2184,19 +2200,22 @@ RED.view = (function() {
* - all "selected"
* - attached to mouse for placing - "IMPORT_DRAGGING"
*/
function importNodes(newNodesStr,touchImport) {
function importNodes(newNodesStr,addNewFlow,touchImport) {
try {
var activeSubflowChanged;
if (activeSubflow) {
activeSubflowChanged = activeSubflow.changed;
}
var result = RED.nodes.import(newNodesStr,true);
var result = RED.nodes.import(newNodesStr,true,addNewFlow);
if (result) {
var new_nodes = result[0];
var new_links = result[1];
var new_workspaces = result[2];
var new_subflows = result[3];
var new_default_workspace = result[4];
if (addNewFlow && new_default_workspace) {
RED.workspaces.show(new_default_workspace.id);
}
var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
var new_node_ids = new_nodes.map(function(n){ return n.id; });
@ -2269,6 +2288,9 @@ RED.view = (function() {
subflows:new_subflows,
dirty:RED.nodes.dirty()
};
if (new_ms.length === 0) {
RED.nodes.dirty(true);
}
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
@ -2373,6 +2395,46 @@ RED.view = (function() {
}
}
return result;
},
reveal: function(id) {
if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
RED.workspaces.show(id);
} else {
var node = RED.nodes.node(id);
if (node._def.category !== 'config' && node.z) {
node.highlighted = true;
node.dirty = true;
RED.workspaces.show(node.z);
RED.view.redraw();
var screenSize = [$("#chart").width(),$("#chart").height()];
var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
if (node.x < scrollPos[0] || node.y < scrollPos[1] || node.x > screenSize[0]+scrollPos[0] || node.y > screenSize[1]+scrollPos[1]) {
var deltaX = '-='+((scrollPos[0] - node.x) + screenSize[0]/2);
var deltaY = '-='+((scrollPos[1] - node.y) + screenSize[1]/2);
$("#chart").animate({
scrollLeft: deltaX,
scrollTop: deltaY
},200);
}
var flash = 22;
var flashFunc = function() {
flash--;
node.highlighted = !node.highlighted;
node.dirty = true;
RED.view.redraw();
if (flash >= 0) {
setTimeout(flashFunc,100);
}
}
flashFunc();
} else if (node._def.category === 'config') {
RED.sidebar.config.show(id);
}
}
}
};
})();

View File

@ -20,7 +20,7 @@ RED.workspaces = (function() {
var activeWorkspace = 0;
var workspaceIndex = 0;
function addWorkspace(ws) {
function addWorkspace(ws,skipHistoryEntry) {
if (ws) {
workspace_tabs.addTab(ws);
workspace_tabs.resize();
@ -34,10 +34,13 @@ RED.workspaces = (function() {
RED.nodes.addWorkspace(ws);
workspace_tabs.addTab(ws);
workspace_tabs.activateTab(tabId);
if (!skipHistoryEntry) {
RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
}
}
return ws;
}
function deleteWorkspace(ws) {
if (workspace_tabs.count() == 1) {
return;
@ -90,11 +93,11 @@ RED.workspaces = (function() {
node: workspace,
dirty: RED.nodes.dirty()
}
workspace.changed = true;
RED.history.push(historyEvent);
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
}
RED.tray.close();
}
@ -110,6 +113,7 @@ RED.workspaces = (function() {
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
dialogForm.submit(function(e) { e.preventDefault();});
$("#node-input-name").val(workspace.label);
RED.text.bidi.prepareInput($("#node-input-name"))
dialogForm.i18n();
},
close: function() {
@ -133,6 +137,7 @@ RED.workspaces = (function() {
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
RED.events.emit("workspace:change",event);
window.location.hash = 'flow/'+tab.id;
RED.sidebar.config.refresh();
},
ondblclick: function(tab) {
@ -143,31 +148,26 @@ RED.workspaces = (function() {
}
},
onadd: function(tab) {
RED.menu.addItem("menu-item-workspace",{
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label,
onselect:function() {
workspace_tabs.activateTab(tab.id);
}
});
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
},
onremove: function(tab) {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
},
onreorder: function(oldOrder, newOrder) {
RED.history.push({t:'reorder',order:oldOrder,dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
setWorkspaceOrder(newOrder);
},
minimumActiveTabWidth: 150
minimumActiveTabWidth: 150,
scrollable: true,
addButton: function() {
addWorkspace();
}
});
}
function init() {
createWorkspaceTabs();
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
RED.events.on("sidebar:resize",workspace_tabs.resize);
RED.menu.setAction('menu-item-workspace-delete',function() {
@ -218,6 +218,8 @@ RED.workspaces = (function() {
var sf = RED.nodes.subflow(id);
if (sf) {
addWorkspace({type:"subflow",id:id,icon:"red/images/subflow_tab.png",label:sf.name, closeable: true});
} else {
return;
}
}
workspace_tabs.activateTab(id);
@ -225,7 +227,6 @@ RED.workspaces = (function() {
refresh: function() {
RED.nodes.eachWorkspace(function(ws) {
workspace_tabs.renameTab(ws.id,ws.label);
$("#menu-item-workspace-menu-"+ws.id.replace(".","-")).text(ws.label);
})
RED.nodes.eachSubflow(function(sf) {

View File

@ -20,6 +20,7 @@ $form-placeholder-color: #bbbbbb;
$form-text-color: #444;
$form-input-focus-color: rgba(85,150,230,0.8);
$form-input-border-color: #ccc;
$form-input-border-selected-color: #aaa;
$node-selected-color: #ff7f0e;
@ -42,13 +43,17 @@ $palette-header-background: #f3f3f3;
$workspace-button-background: #fff;
$workspace-button-background-hover: #ddd;
$workspace-button-background-active: #efefef;
$workspace-button-color: #999;
$workspace-button-color: #888;
$workspace-button-color-disabled: #ccc;
$workspace-button-color-focus: #999;
$workspace-button-color-hover: #666;
$workspace-button-color-active: #666;
$workspace-button-color-selected: #AAA;
$workspace-button-toggle-color: #999;
$workspace-button-toggle-color-selected: #888;
$workspace-button-toggle-color-disabled: #ddd;
$workspace-button-color-focus-outline: rgba(85,150,230,0.2);
$typedInput-button-background: #efefef;
@ -61,4 +66,4 @@ $editor-button-background-primary-hover: #6E0A1E;
$editor-button-color: #999;
$editor-button-background: #fff;
$shade-color: rgba(220,220,220,0.5);
$shade-color: rgba(100,100,100,0.5);

165
editor/sass/diff.scss Normal file
View File

@ -0,0 +1,165 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.
**/
#node-dialog-view-diff {
height: 600px;
.red-ui-editableList-container {
border-radius:1px;
padding:0;
}
ol {
position: absolute;
top:10px;
bottom:10px;
left:10px;
right:10px;
li {
padding: 0px;
border: none;
}
}
.red-ui-editableList-item-content {
padding: 5px;
}
}
.node-diff-tab {
border: 1px solid $secondary-border-color;
border-radius: 3px;
&.collapsed {
.node-diff-tab-title > .node-diff-chevron {
transform: rotate(-90deg);
}
.node-diff-node-entry {
display: none;
}
}
}
.node-diff-tab-stats {
position: absolute;
left: 50%;
}
.node-diff-chevron {
width: 15px;
text-align: center;
margin: 3px 5px 3px 5px;
transition: transform 0.1s ease-in-out;
}
.node-diff-node-entry {
padding: 0 0 0 5px;
&:not(:last-child) {
border-bottom: 1px solid $secondary-border-color;
}
&.collapsed {
.node-diff-chevron {
transform: rotate(-90deg);
}
.node-diff-node-entry-properties {
display: none;
}
}
table {
border-collapse: collapse;
width: 100%;
table-layout:fixed;
}
td, th {
border: 1px solid $secondary-border-color;
padding: 3px 5px;
text-align: left;
}
td:nth-child(1) {
width: 150px;
}
td:not(:first-child) {
width: calc(50% - 150px);
}
}
.node-diff-column {
display:inline-block;
height:100%;
width:50%;
box-sizing: border-box;
white-space:nowrap;
overflow: hidden;
&:first-child {
border-right: 1px solid $secondary-border-color
}
}
.node-diff-tab-title {
padding: 3px 3px 3px 0;
background: #f6f6f6;
cursor: pointer;
}
.node-diff-node-entry-node {
vertical-align: middle;
display: inline-block;
margin: 5px;
width: 24px;
height: 20px;
background: #ddd;
border-radius: 2px;
border: 1px solid #999;
background-position: 5% 50%;
background-repeat: no-repeat;
background-size: contain;
position: relative;
.palette-icon {
width: 16px;
}
.palette_icon_container {
width: 24px;
}
}
.node-diff-node-entry-title {
cursor: pointer;
}
.node-diff-node-entry-properties {
margin-left: 30px;
margin-right: 8px;
margin-bottom:8px;
color: #666;
}
.node-diff-node-description {
color: $form-text-color;
margin-left: 5px;
margin-right: 5px;
padding-top: 5px;
display: inline-block;
&:after {
content: "";
display: table;
clear: both;
}
}
.node-diff-count { color: #999}
.node-diff-added { color: #009900}
.node-diff-deleted { color: #f80000}
.node-diff-changed { color: #f89406}
.node-diff-conflicted { color: purple}

View File

@ -76,16 +76,16 @@
font-size: 14px;
padding: 6px 14px;
margin-right: 8px;
color: $editor-button-color;
color: $editor-button-color !important;
background: $editor-button-background;
&.primary {
border-color: $editor-button-background-primary;
color: $editor-button-color-primary;
color: $editor-button-color-primary !important;
background: $editor-button-background-primary;
&.disabled, &.ui-state-disabled {
background: none;
color: $editor-button-color;
color: $editor-button-color !important;
border-color: $form-input-border-color;
}
&:not(.disabled):not(.ui-button-disabled):hover {
@ -167,7 +167,7 @@
background: $background-color;
color: $workspace-button-color;
}
#editor-shade, #header-shade {
#palette-shade, #editor-shade, #header-shade, #sidebar-shade {
position: absolute;
top:0;
bottom:0;
@ -176,6 +176,11 @@
background: $shade-color;
z-index: 2;
}
#sidebar-shade {
left: -8px;
top: -1px;
bottom: -1px;
}
.dialog-form,#dialog-form, #dialog-config-form {
@ -196,7 +201,7 @@
display: inline-block;
width: 100px;
}
.form-row input {
.form-row input, .form-row div[contenteditable="true"] {
width:70%;
}
@ -230,7 +235,12 @@
font-size: 13px;
border-radius: 4px;
padding: 0 10px;
&.toggle {
@include workspace-button-toggle;
}
}
.editor-button-small {
height: 20px;
line-height: 18px;
@ -239,6 +249,17 @@
padding: 0 5px;
}
.dialog-form {
.button-group {
.editor-button {
&:first-child {
}
}
}
}
#node-config-dialog-scope-container {
cursor: auto;
float: right;
@ -255,7 +276,7 @@
margin: 1px 0 0 0;
padding: 0;
height: 22px;
width: 150px;
width: 200px;
}
#node-config-dialog-user-count {

View File

@ -157,6 +157,8 @@
stroke: $node-selected-color !important;
}
.node_highlighted {
border-color: #dd1616 !important;
border-style: dashed !important;
stroke: #dd1616;
stroke-width: 2;
stroke-dasharray: 10, 4;

View File

@ -26,6 +26,7 @@
button,
input,
select,
div[contenteditable="true"],
textarea {
margin: 0;
font-size: 100%;
@ -33,12 +34,14 @@ textarea {
}
button,
div[contenteditable="true"],
input {
*overflow: visible;
line-height: normal;
}
button::-moz-focus-inner,
div[contenteditable="true"]::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0;
@ -110,6 +113,7 @@ legend small {
label,
input,
div[contenteditable="true"],
button,
select,
textarea {
@ -119,6 +123,7 @@ textarea {
}
input,
div[contenteditable="true"],
button,
select,
textarea {
@ -146,6 +151,7 @@ input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"],
div[contenteditable="true"],
.uneditable-input {
box-sizing: border-box;
display: inline-block;
@ -161,6 +167,7 @@ input[type="color"],
input,
textarea,
div[contenteditable="true"],
.uneditable-input {
width: 206px;
}
@ -184,6 +191,7 @@ input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"],
div[contenteditable="true"],
.uneditable-input {
background-color: #ffffff;
border: 1px solid $form-input-border-color;
@ -207,6 +215,7 @@ input[type="url"]:focus,
input[type="search"]:focus,
input[type="tel"]:focus,
input[type="color"]:focus,
div[contenteditable="true"]:focus,
.uneditable-input:focus {
border-color: $form-input-focus-color;
outline: 0;
@ -294,11 +303,13 @@ textarea:-moz-placeholder {
}
input:-ms-input-placeholder,
div[contenteditable="true"]:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: $form-placeholder-color;
}
input::-webkit-input-placeholder,
div[contenteditable="true"]::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
color: $form-placeholder-color;
}
@ -384,6 +395,7 @@ textarea[class*="span"],
input,
textarea,
div[contenteditable="true"],
.uneditable-input {
margin-left: 0;
}
@ -515,12 +527,14 @@ input[type="checkbox"][readonly] {
.control-group.warning .checkbox,
.control-group.warning .radio,
.control-group.warning input,
.control-group.warning div[contenteditable="true"],
.control-group.warning select,
.control-group.warning textarea {
color: #c09853;
}
.control-group.warning input,
.control-group.warning div[contenteditable="true"],
.control-group.warning select,
.control-group.warning textarea {
border-color: #c09853;
@ -530,6 +544,7 @@ input[type="checkbox"][readonly] {
}
.control-group.warning input:focus,
.control-group.warning div[contenteditable="true"]:focus,
.control-group.warning select:focus,
.control-group.warning textarea:focus {
border-color: #a47e3c;
@ -554,12 +569,14 @@ input[type="checkbox"][readonly] {
.control-group.error .checkbox,
.control-group.error .radio,
.control-group.error input,
.control-group.error div[contenteditable="true"],
.control-group.error select,
.control-group.error textarea {
color: #b94a48;
}
.control-group.error input,
.control-group.error div[contenteditable="true"],
.control-group.error select,
.control-group.error textarea {
border-color: #b94a48;
@ -569,6 +586,7 @@ input[type="checkbox"][readonly] {
}
.control-group.error input:focus,
.control-group.error div[contenteditable="true"]:focus,
.control-group.error select:focus,
.control-group.error textarea:focus {
border-color: #953b39;
@ -593,12 +611,14 @@ input[type="checkbox"][readonly] {
.control-group.success .checkbox,
.control-group.success .radio,
.control-group.success input,
.control-group.success div[contenteditable="true"],
.control-group.success select,
.control-group.success textarea {
color: #468847;
}
.control-group.success input,
.control-group.success div[contenteditable="true"],
.control-group.success select,
.control-group.success textarea {
border-color: #468847;
@ -608,6 +628,7 @@ input[type="checkbox"][readonly] {
}
.control-group.success input:focus,
.control-group.success div[contenteditable="true"]:focus,
.control-group.success select:focus,
.control-group.success textarea:focus {
border-color: #356635;
@ -632,12 +653,14 @@ input[type="checkbox"][readonly] {
.control-group.info .checkbox,
.control-group.info .radio,
.control-group.info input,
.control-group.info div[contenteditable="true"],
.control-group.info select,
.control-group.info textarea {
color: #3a87ad;
}
.control-group.info input,
.control-group.info div[contenteditable="true"],
.control-group.info select,
.control-group.info textarea {
border-color: #3a87ad;
@ -647,6 +670,7 @@ input[type="checkbox"][readonly] {
}
.control-group.info input:focus,
.control-group.info div[contenteditable="true"]:focus,
.control-group.info select:focus,
.control-group.info textarea:focus {
border-color: #2d6987;
@ -663,6 +687,7 @@ input[type="checkbox"][readonly] {
}
input:focus:invalid,
div[contenteditable="true"]:focus:invalid,
textarea:focus:invalid,
select:focus:invalid {
color: #b94a48;
@ -670,6 +695,7 @@ select:focus:invalid {
}
input:focus:invalid:focus,
div[contenteditable="true"]:focus:invalid:focus,
textarea:focus:invalid:focus,
select:focus:invalid:focus {
border-color: #e9322d;
@ -727,6 +753,8 @@ select:focus:invalid:focus {
.input-append input,
.input-prepend input,
.input-append div[contenteditable="true"],
.input-prepend div[contenteditable="true"],
.input-append select,
.input-prepend select,
.input-append .uneditable-input,
@ -740,6 +768,8 @@ select:focus:invalid:focus {
.input-append input,
.input-prepend input,
.input-append div[contenteditable="true"],
.input-prepend div[contenteditable="true"],
.input-append select,
.input-prepend select,
.input-append .uneditable-input,
@ -755,6 +785,8 @@ select:focus:invalid:focus {
.input-append input:focus,
.input-prepend input:focus,
.input-append div[contenteditable="true"]:focus,
.input-prepend div[contenteditable="true"]:focus,
.input-append select:focus,
.input-prepend select:focus,
.input-append .uneditable-input:focus,
@ -809,6 +841,7 @@ select:focus:invalid:focus {
}
.input-append input,
.input-append div[contenteditable="true"],
.input-append select,
.input-append .uneditable-input {
-webkit-border-radius: 4px 0 0 4px;
@ -839,6 +872,7 @@ select:focus:invalid:focus {
}
.input-prepend.input-append input,
.input-prepend.input-append div[contenteditable="true"],
.input-prepend.input-append select,
.input-prepend.input-append .uneditable-input {
-webkit-border-radius: 0;
@ -923,6 +957,9 @@ input.search-query {
.form-search input,
.form-inline input,
.form-horizontal input,
.form-search div[contenteditable="true"],
.form-inline div[contenteditable="true"],
.form-horizontal div[contenteditable="true"],
.form-search textarea,
.form-inline textarea,
.form-horizontal textarea,
@ -1045,3 +1082,8 @@ legend + .control-group {
.form-horizontal .form-actions {
padding-left: 180px;
}
.form-row div[contenteditable="true"] {
white-space: nowrap;
overflow: hidden;
}

View File

@ -18,11 +18,11 @@
font-size: 14px !important;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
}
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {
.ui-widget input, .ui-widget div[contenteditable="true"], .ui-widget select, .ui-widget textarea, .ui-widget button {
font-size: 14px !important;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
}
.ui-widget input {
.ui-widget input, .ui-widget div[contenteditable="true"] {
box-shadow: none;
}
@ -92,13 +92,18 @@
&.primary {
border-color: $editor-button-background-primary;
color: $editor-button-color-primary;
color: $editor-button-color-primary !important;
background: $editor-button-background-primary;
&:not(.disabled):hover {
border-color: $editor-button-background-primary-hover;
background: $editor-button-background-primary-hover;
color: $editor-button-color-primary !important;
}
&.disabled {
border-color: $form-input-border-color;
color: $workspace-button-color-disabled !important;
background: $editor-button-background;
}
}
&.disabled {
background: none;

View File

@ -1,5 +1,5 @@
/**
* Copyright 2015 IBM Corp.
* Copyright 2015, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,6 +22,14 @@
user-select: none;
}
@mixin enable-selection {
-webkit-user-select: auto;
-khtml-user-select: auto;
-moz-user-select: auto;
-ms-user-select: auto;
user-select: auto;
}
@mixin component-border {
border: 1px solid $primary-border-color;
box-sizing: border-box;
@ -32,38 +40,46 @@
@include disable-selection;
box-sizing: border-box;
display: inline-block;
color: $workspace-button-color;
color: $workspace-button-color !important;
background: $workspace-button-background;
border: 1px solid $form-input-border-color;
text-align: center;
margin:0;
text-decoration: none;
cursor:pointer;
&.disabled {
cursor: default;
color: $workspace-button-color-disabled;
color: $workspace-button-color-disabled !important;
}
&:hover, &:focus {
text-decoration: none;
}
&:not(.disabled):hover {
text-decoration: none;
color: $workspace-button-color-hover;
color: $workspace-button-color-hover !important;
background: $workspace-button-background-hover;
}
&:not(.disabled):focus {
color: $workspace-button-color-focus;
text-decoration: none;
color: $workspace-button-color-focus !important;
}
&:not(.disabled):active {
color: $workspace-button-color-active;
color: $workspace-button-color-active !important;
background: $workspace-button-background-active;
text-decoration: none;
}
&.selected:not(.disabled) {
color: $workspace-button-color-selected;
color: $workspace-button-color-selected !important;
background: $workspace-button-background-active;
cursor: default;
}
.button-group &:not(:first-child) {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.button-group &:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:focus {
@ -72,12 +88,18 @@
}
@mixin workspace-button-toggle {
@include workspace-button;
color: $workspace-button-color-selected;
background: $workspace-button-background-active;
color: $workspace-button-toggle-color !important;
background:$workspace-button-background-active;
margin-bottom: 1px;
&.selected:not(.disabled) {
color: $workspace-button-color;
color: $workspace-button-toggle-color-selected !important;
background: $workspace-button-background;
border-bottom-width: 2px;
border-bottom-color: $form-input-border-selected-color;
margin-bottom: 0;
}
&.disabled {
color: $workspace-button-toggle-color-disabled !important;
}
}

View File

@ -0,0 +1,232 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.
**/
#palette-editor {
text-align: left;
display: none;
position: absolute;
top: 0px;
right: 0;
bottom: 25px;
left:0;
padding: 0;
box-sizing:border-box;
background: #fff;
.red-ui-editableList-container {
border: none;
border-radius: 0;
padding: 0px;
li {
// border: none;
// border-top: 1px solid $primary-border-color;
padding: 0px;
.disabled {
background: #f3f3f3;
.palette-module-name {
font-style: italic;
color: #aaa;
}
.palette-module-version {
color: #aaa;
}
}
.red-ui-editableList-item-content {
padding: 12px 8px;
}
&:last-child {
// border-bottom: 1px solid $primary-border-color;
}
}
}
.palette-editor-tab {
position:absolute;
top:115px;
left:0;
right:0;
bottom:0
}
.palette-editor-toolbar {
background: #f3f3f3;
box-sizing: border-box;
padding: 8px 10px;
border-bottom: 1px solid $primary-border-color;
text-align: right;
.button-group {
margin-right: 10px;
}
}
.palette-module-button-group {
position: absolute;
right: 0;
bottom: 0;
a {
margin-left: 5px;
}
}
.palette-module-shade {
position: absolute;
top: 0;
bottom:0;
left:0;
right:0;
background: $shade-color;
text-align: center;
padding-top: 20px;
}
#palette-module-install-shade {
padding-top: 80px;
}
.palette-module-shade-status {
color: #666;
}
.palette-module-meta {
color: #666;
position: relative;
&.disabled {
color: #ccc;
}
.fa {
width: 15px;
text-align: center;
margin-right: 5px;
}
}
.palette-module-name {
white-space: nowrap;
@include enable-selection;
}
.palette-module-version, .palette-module-updated, .palette-module-link {
font-style:italic;
font-size: 0.8em;
@include enable-selection;
}
.palette-module-updated {
margin-left: 10px;
}
.palette-module-link {
margin-left: 5px;
}
.palette-module-description {
margin-left: 20px;
font-size: 0.9em;
color: #999;
}
.palette-module-link {
}
.palette-module-set-button-group {
}
.palette-module-count {
border-radius: 4px;
background: #eee;
padding: 2px 8px;
font-size: 12px;
}
.palette-module-content {
display: none;
padding: 10px 3px;
}
i.fa.palette-module-node-chevron {
width: 8px;
margin-right: 0;
transform: rotate(0deg);
transition: transform 0.2s ease-in-out;
}
.expanded {
i.fa.palette-module-node-chevron {
transform: rotate(90deg);
}
.palette-module-set-button {
background:#f3f3f3 !important;
}
}
.palette-module-set {
border:1px solid $secondary-border-color;
border-radius: 0;
padding: 5px;
position: relative;
&:not(:last-child) {
border-bottom: none;
}
&:first-child {
border-top-right-radius: 2px;
border-top-left-radius: 2px;
}
&:last-child {
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
}
}
.palette-module-type {
color: #666;
padding-left: 5px;
font-size: 0.9em;
@include enable-selection;
}
.palette-module-type-swatch {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 3px;
vertical-align: middle;
margin-right: 5px;
background: #fff;
border: 1px solid #fff;
}
.palette-module-set-button-group {
position: absolute;
right: 4px;
top: 4px;
}
.palette-module-set-disabled {
background: #eee;
.palette-module-type {
color: #999;
}
}
.palette-module-more {
padding: 0 !important;
margin-top: 10px;
margin-bottom: 10px;
background: $tab-background-inactive;
a {
display: block;
text-align: center;
padding: 12px 8px;
color: #AD1625;
&:hover {
text-decoration: none;
background: $tab-background-hover;
}
}
}
}

View File

@ -25,10 +25,25 @@
text-align: center;
@include disable-selection;
@include component-border;
transition: width 0.2s ease-in-out;
}
.palette-expanded {
& #palette {
width: 380px;
box-shadow: 1px 0 6px rgba(0,0,0,0.1);
}
& #workspace { left: 379px !important; }
& #palette-collapse-all { display: none; }
& #palette-expand-all { display: none; }
& #palette-container { display: none !important; }
& #palette-search { display: none !important; }
& #palette-edit { background: $workspace-button-background-active }
& #palette-editor { display: block !important }
}
.palette-scroll {
display: none;
position: absolute;
top: 35px;
right: 0;
@ -38,15 +53,11 @@
overflow-y: auto;
box-sizing:border-box;
}
.palette-spinner {
padding-top: 40px;
#palette > .palette-spinner {
padding-top: 80px;
}
#palette-search {
position: absolute;
display: none;
top: 0;
left:0;
right:0;
.palette-search {
position: relative;
overflow: hidden;
background: #ffffff;
text-align: center;
@ -55,48 +66,7 @@
border-bottom: 1px solid $primary-border-color;
box-sizing:border-box;
}
#palette-search i {
font-size: 10px;
color: #666;
}
#palette-search i.fa-search {
position: absolute;
pointer-events: none;
left: 12px;
top: 12px;
}
#palette-search i.fa-times {
position: absolute;
right: 7px;
top: 12px;
}
#palette-search-clear {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 20px;
display: none;
}
#palette-search input {
border-radius: 0;
border: none;
width: 100%;
box-shadow: none;
-webkit-box-shadow: none;
padding: 3px 17px 3px 22px;
margin: 0px;
height: 30px;
box-sizing:border-box;
}
#palette-search input:focus {
border: none;
box-shadow: none;
-webkit-box-shadow: none;
}
#palette-footer {
@include component-footer;
}

106
editor/sass/search.scss Normal file
View File

@ -0,0 +1,106 @@
/**
* Copyright 2016 IBM Corp.
*
* 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-ui-search {
z-index:1000;
display: none;
position: absolute;
width: 500px;
background: white;
left: 50%;
margin-left: -250px;
top: 0px;
border: 1px solid $primary-border-color;
box-shadow: 0 0 10px rgba(0,0,0,0.4);
ol {
}
}
.red-ui-search-container {
padding: 3px;
border-bottom: 1px solid $secondary-border-color;
}
.red-ui-search-results-container {
position:relative;
height: 300px;
padding: 5px;
background: $background-color;
.red-ui-editableList-container {
background: white;
border-radius: 2px;
padding: 0;
background: $background-color;
li {
padding: 0;
&.selected .red-ui-search-result {
border-color: $primary-border-color;
}
}
}
}
.red-ui-search-result {
padding: 8px 2px 8px 5px;
display: block;
cursor: pointer;
color: $form-text-color;
border: 2px solid white;
&:hover {
text-decoration: none;
border-color: $primary-border-color;
color: $form-text-color;
}
}
.red-ui-search-result-node {
display: inline-block;
width: 30px;
float:left;
height: 25px;
background: #ddd;
border-radius: 5px;
border: 1px solid #999;
background-position: 5% 50%;
background-repeat: no-repeat;
background-size: contain;
position: relative;
}
.red-ui-search-result-description {
margin-left: 40px;
margin-right: 5px;
}
.red-ui-search-result-node-label {
color: #222;
}
.red-ui-search-result-node-type {
font-style: italic;
font-size: 0.9em;
}
.red-ui-search-result-node-flow {
float:right;
font-size: 0.8em;
}
.red-ui-search-result-node-id {
display:none;
font-size: 0.8em;
}
.red-ui-search-empty {
padding: 10px;
text-align: center;
font-style: italic;
color: $form-placeholder-color;
}

View File

@ -34,15 +34,20 @@
@import "editor";
@import "library";
@import "search";
@import "tabs";
@import "tab-config";
@import "tab-info";
@import "popover";
@import "flow";
@import "palette-editor";
@import "diff";
@import "typedInput";
@import "editableList";
@import "ui/common/editableList";
@import "ui/common/searchBox";
@import "ui/common/typedInput";
@import "dragdrop";

View File

@ -1,5 +1,5 @@
/**
* Copyright 2015 IBM Corp.
* Copyright 2015, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,20 +14,34 @@
* limitations under the License.
**/
ul.red-ui-tabs {
.red-ui-tabs {
position: relative;
background: #fff;
overflow: hidden;
height: 35px;
box-sizing: border-box;
.red-ui-tabs-scroll-container {
height: 60px;
overflow-x: scroll;
overflow-y: hidden;
&::-webkit-scrollbar {
display: none;
}
}
& ul {
//background: #9999ff;
list-style-type: none;
padding:0;
margin: 0;
display: block;
height: 35px;
box-sizing:border-box;
white-space: nowrap;
border-bottom: 1px solid $primary-border-color;
background: #fff;
white-space: nowrap;
@include disable-selection;
}
ul.red-ui-tabs li {
li {
box-sizing: border-box;
display: inline-block;
border-left: 1px solid $primary-border-color;
@ -42,19 +56,132 @@ ul.red-ui-tabs li {
width: 14%;
overflow: hidden;
white-space: nowrap;
}
position: relative;
ul.red-ui-tabs li a.red-ui-tab-label {
a.red-ui-tab-label {
display: block;
font-size: 14px;
padding-left: 12px;
width: 100%;
height: 100%;
color: #666;
}
a:hover {
text-decoration: none;
}
a:focus {
text-decoration: none;
}
&:not(.active) a:hover+a.red-ui-tab-close {
background: $tab-background-hover;
}
&.active {
background: $tab-background-active;
font-weight: bold;
border-bottom: 1px solid #fff;
z-index: 2;
a {
color: #333;
}
a.red-ui-tab-close {
color: #aaa;
background: $tab-background-active;
&:hover {
background: $workspace-button-background-hover !important;
color: $workspace-button-color-hover;
}
}
.red-ui-tab-icon {
opacity: 0.2;
}
}
&:not(.active) a:hover {
color: $workspace-button-color-hover;
background: $tab-background-hover;
}
}
}
&.red-ui-tabs-scrollable {
padding-left: 21px;
padding-right: 21px;
}
&.red-ui-tabs-add {
padding-right: 35px;
}
&.red-ui-tabs-add.red-ui-tabs-scrollable {
padding-right: 59px;
}
}
ul.red-ui-tabs li {
position: relative;
.red-ui-tab-button {
position: absolute;
box-sizing: border-box;
top: 0;
right: 0;
height: 35px;
background: #fff;
border-bottom: 1px solid $primary-border-color;
z-index: 2;
a {
@include workspace-button;
line-height: 32px;
height: 32px;
width: 32px;
margin-top: 3px;
margin-right:3px;
margin-left:3px;
border: 1px solid $primary-border-color;
z-index: 2;
}
}
.red-ui-tab-scroll {
width: 21px;
top: 0;
a {
height: 35px;
width: 21px;
display: block;
color: $link-color;
font-size: 22px;
text-align: center;
margin:0;
border-left: none;
border-right: none;
border-top: none;
}
}
.red-ui-tab-scroll-left {
left:0;
a {
border-right: 1px solid $primary-border-color;
// box-shadow: 8px 0px 5px -2px rgba(0,0,0,0.1);
}
}
.red-ui-tab-scroll-right {
right: 0px;
a {
border-left: 1px solid $primary-border-color;
// box-shadow: -8px 0px 5px -2px rgba(0,0,0,0.1);
}
}
.red-ui-tabs.red-ui-tabs-add .red-ui-tab-scroll-right {
right: 38px;
}
.red-ui-tab-icon {
margin-left: -8px;
margin-right: 3px;
margin-top: -2px;
opacity: 0.1;
width: 20px;
height: 20px;
vertical-align: middle;
}
.red-ui-tabs-badges {
position: absolute;
top:2px;
@ -96,51 +223,3 @@ ul.red-ui-tabs li {
opacity: 1;
}
}
ul.red-ui-tabs li:not(.active) a:hover+a.red-ui-tab-close {
background: $tab-background-hover;
}
ul.red-ui-tabs li.active a.red-ui-tab-close {
color: #aaa;
background: $tab-background-active;
&:hover {
background: $workspace-button-background-hover !important;
color: $workspace-button-color-hover;
}
}
ul.red-ui-tabs li a:hover {
text-decoration: none;
}
ul.red-ui-tabs li:not(.active) a:hover {
color: $workspace-button-color-hover;
background: $tab-background-hover;
}
ul.red-ui-tabs li a:focus {
text-decoration: none;
}
ul.red-ui-tabs li.active {
background: $tab-background-active;
font-weight: bold;
border-bottom: 1px solid #fff;
z-index: 2;
}
ul.red-ui-tabs li.active a {
color: #333;
}
.red-ui-tab-icon {
margin-left: -8px;
margin-right: 3px;
margin-top: -2px;
opacity: 0.1;
width: 20px;
height: 20px;
vertical-align: middle;
}
ul.red-ui-tabs li.active .red-ui-tab-icon {
opacity: 0.2;
}

View File

@ -0,0 +1,70 @@
/**
* Copyright 2016 IBM Corp.
*
* 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-ui-searchBox-container {
position: relative;
i {
font-size: 10px;
color: #666;
}
i.fa-search {
position: absolute;
pointer-events: none;
left: 8px;
top: 9px;
}
i.fa-times {
position: absolute;
right: 5px;
top: 9px;
}
input {
border-radius: 0;
border: none;
width: 100%;
box-shadow: none;
-webkit-box-shadow: none;
padding: 3px 17px 3px 22px;
margin: 0px;
height: 30px;
box-sizing:border-box;
&:focus {
border: none;
box-shadow: none;
-webkit-box-shadow: none;
}
}
a {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 20px;
display: none;
}
.red-ui-searchBox-resultCount {
position: absolute;
right: 18px;
top: 4px;
background: #eee;
color: #666;
padding: 1px 8px;
font-size: 9px;
border-radius: 4px;
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.
**/
@import "colors";
@import "mixins";
@import "forms";
@import "jquery";
@import "typedInput";
@import "editableList";

View File

@ -40,39 +40,14 @@
right: 322px;
overflow: hidden;
@include component-border;
transition: left 0.2s ease-in-out;
}
.workspace-footer-button {
@include component-footer-button;
}
#workspace-tabs {
margin-right: 35px;
}
#workspace-add-tab {
position: absolute;
box-sizing: border-box;
top: 0;
right: 0;
height: 35px;
width: 35px;
background: #fff;
border-bottom: 1px solid $primary-border-color;
}
#btn-workspace-add-tab {
@include workspace-button;
line-height: 32px;
height: 32px;
width: 32px;
margin-top: 3px;
margin-right:3px;
border: 1px solid $primary-border-color;
}
#workspace-footer {
@include component-footer;
}

View File

@ -6,7 +6,7 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<!--
Copyright 2013, 2015 IBM Corp.
Copyright 2013, 2016 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -43,22 +43,8 @@
<div id="header-shade" class="hide"></div>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-search">
<i class="fa fa-search"></i><input id="palette-search-input" type="text" data-i18n="[placeholder]palette.filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
</div>
<div id="palette-container" class="palette-scroll"></div>
<div id="palette-footer">
<a class="palette-button" id="palette-collapse-all" href="#"><i class="fa fa-angle-double-up"></i></a>
<a class="palette-button" id="palette-expand-all" href="#"><i class="fa fa-angle-double-down"></i></a>
</div>
</div><!-- /palette -->
<div id="workspace">
<ul id="workspace-tabs"></ul>
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart" tabindex="1"></div>
<div id="workspace-toolbar"></div>
<div id="workspace-footer">
@ -66,13 +52,30 @@
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
</div>
</div>
<div id="editor-shade" class="hide"></div>
</div>
<div id="editor-stack"></div>
<div id="palette">
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-search" class="palette-search hide">
<input type="text" data-i18n="[placeholder]palette.filter"></input>
</div>
<div id="palette-editor">
<div class="editor-tray-header"><div class="editor-tray-titlebar"><ul class="editor-tray-breadcrumbs"><li data-i18n="palette.editor.title"></li></ul></div><div class="editor-tray-toolbar"><button id="palette-editor-close" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only primary" role="button" aria-disabled="false" data-i18n="common.label.done"></button></div></div>
<ul id="palette-editor-tabs"></ul>
</div>
<div id="palette-container" class="palette-scroll hide"></div>
<div id="palette-footer">
<a class="palette-button" id="palette-collapse-all" href="#"><i class="fa fa-angle-double-up"></i></a>
<a class="palette-button" id="palette-expand-all" href="#"><i class="fa fa-angle-double-down"></i></a>
</div>
<div id="palette-shade" class="hide"></div>
</div><!-- /palette -->
<div id="sidebar">
<ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div>
<div id="sidebar-footer"></div>
<div id="sidebar-shade" class="hide"></div>
</div>
<div id="sidebar-separator"></div>
@ -90,8 +93,13 @@
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
</div>
<div id="node-dialog-confirm-deploy-conflict" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.conflict;[append]deploy.confirm.confirm">
</div>
</form>
</div>
<div id="node-dialog-view-diff" class="hide">
<ol id="node-dialog-view-diff-diff"></ol>
</div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">

View File

@ -168,7 +168,7 @@
msg.onclick = function() {
var node = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (node) {
RED.workspaces.show(node.z);
RED.view.reveal(node.id);
}
};

View File

@ -175,7 +175,7 @@ module.exports = function(RED) {
node.log(RED._("mqtt.state.connected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
for (var id in node.users) {
if (node.users.hasOwnProperty(id)) {
node.users[id].status({fill:"green",shape:"dot",text:"common.status.connected"});
node.users[id].status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
}
}
// Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
@ -205,7 +205,7 @@ module.exports = function(RED) {
node.client.on("reconnect", function() {
for (var id in node.users) {
if (node.users.hasOwnProperty(id)) {
node.users[id].status({fill:"yellow",shape:"ring",text:"common.status.connecting"});
node.users[id].status({fill:"yellow",shape:"ring",text:"node-red:common.status.connecting"});
}
}
})
@ -216,7 +216,7 @@ module.exports = function(RED) {
node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
for (var id in node.users) {
if (node.users.hasOwnProperty(id)) {
node.users[id].status({fill:"red",shape:"ring",text:"common.status.disconnected"});
node.users[id].status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
}
}
} else if (node.connecting) {
@ -329,7 +329,7 @@ module.exports = function(RED) {
}
var node = this;
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
if (this.topic) {
node.brokerConn.register(this);
this.brokerConn.subscribe(this.topic,this.qos,function(topic,payload,packet) {
@ -341,7 +341,7 @@ module.exports = function(RED) {
node.send(msg);
}, this.id);
if (this.brokerConn.connected) {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
}
}
else {
@ -369,7 +369,7 @@ module.exports = function(RED) {
var node = this;
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
this.on("input",function(msg) {
if (msg.qos) {
msg.qos = parseInt(msg.qos);
@ -391,7 +391,7 @@ module.exports = function(RED) {
}
});
if (this.brokerConn.connected) {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
}
node.brokerConn.register(node);
this.on('close', function(done) {

View File

@ -27,7 +27,7 @@
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="/url">
<div id="node-input-url" contenteditable="true" placeholder="/url"></div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@ -117,7 +117,7 @@ msg.cookies = {
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""},
url: {value:"",required:true},
url: {value:"",required:true,format:"url"},
method: {value:"get",required:true},
swaggerDoc: {type:"swagger-doc", required:false}
},

View File

@ -27,7 +27,7 @@
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="http://">
<div id="node-input-url" contenteditable="true" placeholder="http://"></div>
</div>
<div class="form-row">
@ -102,7 +102,7 @@
name: {value:""},
method:{value:"GET"},
ret: {value:"txt"},
url:{value:""},
url:{value:"",format:"url"},
tls: {type:"tls-config",required: false}
},
credentials: {

View File

@ -74,6 +74,8 @@ module.exports = function(RED) {
var opts = urllib.parse(url);
opts.method = method;
opts.headers = {};
var ctSet = "Content-Type"; // set default camel case
var clSet = "Content-Length";
if (msg.headers) {
for (var v in msg.headers) {
if (msg.headers.hasOwnProperty(v)) {
@ -83,6 +85,8 @@ module.exports = function(RED) {
// function. Otherwise leave them alone.
name = v;
}
else if (name === 'content-type') { ctSet = v; }
else { clSet = v; }
opts.headers[name] = msg.headers[v];
}
}
@ -103,18 +107,27 @@ module.exports = function(RED) {
} else {
payload = JSON.stringify(msg.payload);
if (opts.headers['content-type'] == null) {
opts.headers['content-type'] = "application/json";
opts.headers[ctSet] = "application/json";
}
}
}
if (opts.headers['content-length'] == null) {
if (Buffer.isBuffer(payload)) {
opts.headers['content-length'] = payload.length;
opts.headers[clSet] = payload.length;
} else {
opts.headers['content-length'] = Buffer.byteLength(payload);
opts.headers[clSet] = Buffer.byteLength(payload);
}
}
}
// revert to user supplied Capitalisation if needed.
if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) {
opts.headers[ctSet] = opts.headers['content-type'];
delete opts.headers['content-type'];
}
if (opts.headers.hasOwnProperty('content-length') && (clSet !== 'content-length')) {
opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length'];
}
var urltotest = url;
var noproxy;
if (noprox) {
@ -135,7 +148,6 @@ module.exports = function(RED) {
opts.path = opts.pathname = path;
opts.headers = heads;
opts.method = method;
//console.log(opts);
urltotest = match[0];
}
else { node.warn("Bad proxy url: "+process.env.http_proxy); }

View File

@ -145,7 +145,7 @@
RED.nodes.registerType('websocket-listener',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/),format:"filepath" },
wholemsg: {value:"false"}
},
inputs:0,
@ -179,7 +179,7 @@
RED.nodes.registerType('websocket-client',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/),format:"filepath" },
wholemsg: {value:"false"}
},
inputs:0,
@ -232,7 +232,7 @@
<script type="text/x-red" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input type="text" id="node-config-input-path" placeholder="/ws/example">
<div id="node-config-input-path" contenteditable="true" placeholder="/ws/example"></div>
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
@ -255,7 +255,7 @@
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
<div id="node-config-input-path" contenteditable="true" placeholder="ws://example.com/ws"></div>
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>

View File

@ -17,7 +17,7 @@
<script type="text/x-red" data-template-name="watch">
<div class="form-row node-input-filename">
<label for="node-input-files"><i class="fa fa-file"></i> <span data-i18n="watch.label.files"></span></label>
<input type="text" id="node-input-files" data-i18n="[placeholder]watch.placeholder.files">
<div id="node-input-files" contenteditable="true" tabindex="1" data-i18n="[placeholder]watch.placeholder.files"></div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@ -46,7 +46,7 @@
category: 'advanced-input',
defaults: {
name: {value:""},
files: {value:"",required:true}
files: {value:"",required:true, format:"filepath"}
},
color:"BurlyWood",
inputs:0,

View File

@ -17,7 +17,7 @@
<script type="text/x-red" data-template-name="tail">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="tail.label.filename"></span></label>
<input type="text" id="node-input-filename">
<div id="node-input-filename" contenteditable="true"></div>
</div>
<div class="form-row">
<label for="node-input-filetype"><i class="fa fa-file-text-o"></i> <span data-i18n="tail.label.type"></span></label>
@ -50,7 +50,7 @@
name: {value:""},
filetype: {value:"text"},
split: {value:false},
filename: {value:"",required:true}
filename: {value:"",required:true,format:"filepath"}
},
color:"BurlyWood",
inputs:0,

View File

@ -17,7 +17,7 @@
<script type="text/x-red" data-template-name="file">
<div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input type="text" id="node-input-filename">
<div id="node-input-filename" contenteditable="true"></div>
</div>
<div class="form-row">
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
@ -56,7 +56,7 @@
<script type="text/x-red" data-template-name="file in">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input type="text" id="node-input-filename" data-i18n="[placeholder]file.label.filename">
<div id="node-input-filename" contenteditable="true" data-i18n="[placeholder]file.label.filename"></div>
</div>
<div class="form-row">
<label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
@ -83,7 +83,7 @@
category: 'storage-output',
defaults: {
name: {value:""},
filename: {value:""},
filename: {value:"",format:"filepath"},
appendNewline: {value:true},
createDir: {value:false},
overwriteFile: {value:"false"}
@ -115,7 +115,7 @@
category: 'storage-input',
defaults: {
name: {value:""},
filename: {value:""},
filename: {value:"",format:"filepath"},
format: {value:"utf8"},
},
color:"BurlyWood",

View File

@ -61,7 +61,7 @@
"node-red-node-rbe":"0.1.*"
},
"optionalDependencies": {
"node-red-node-serialport":"0.2.*",
"node-red-node-serialport":"0.3.*",
"bcrypt":"0.8.7"
},
"devDependencies": {

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014, 2015 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -25,13 +25,23 @@ module.exports = {
log = runtime.log;
},
get: function(req,res) {
log.audit({event: "flows.get"},req);
var version = req.get("Node-RED-API-Version")||"v1";
if (version === "v1") {
log.audit({event: "flows.get",version:"v1"},req);
res.json(redNodes.getFlows().flows);
} else if (version === "v2") {
log.audit({event: "flows.get",version:"v2"},req);
res.json(redNodes.getFlows());
} else {
log.audit({event: "flows.get",version:version,error:"bad_api_version"},req);
res.status(400).json({error:"bad_api_version"});
}
},
post: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1";
var flows = req.body;
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
log.audit({event: "flows.set",type:deploymentType},req);
log.audit({event: "flows.set",type:deploymentType,version:version},req);
if (deploymentType === 'reload') {
redNodes.loadFlows().then(function() {
res.status(204).end();
@ -41,8 +51,28 @@ module.exports = {
res.status(500).json({error:"unexpected_error", message:err.message});
});
} else {
redNodes.setFlows(flows,deploymentType).then(function() {
var flowConfig = flows;
if (version === "v2") {
flowConfig = flows.flows;
if (flows.hasOwnProperty('rev')) {
var currentVersion = redNodes.getFlows().rev;
if (currentVersion !== flows.rev) {
//TODO: log warning
return res.status(409).json({error:"version_mismatch"});
}
}
} else if (version !== 'v1') {
log.audit({event: "flows.set",version:version,error:"bad_api_version"},req);
return res.status(400).json({error:"bad_api_version"});
}
redNodes.setFlows(flowConfig,deploymentType).then(function(flowId) {
if (version === "v1") {
res.status(204).end();
} else if (version === "v2") {
res.json({rev:flowId});
} else {
// TODO: invalid version
}
}).otherwise(function(err) {
log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack);

View File

@ -25,7 +25,12 @@
"view": {
"view": "View",
"showGrid": "Show grid",
"snapGrid": "Snap to grid"
"snapGrid": "Snap to grid",
"textDir": "Text Direction",
"defaultDir": "Default",
"ltr": "Left-to-right",
"rtl": "Right-to-left",
"auto": "Contextual"
},
"sidebar": {
"show": "Show sidebar"
@ -34,6 +39,7 @@
"displayConfig": "Configuration nodes",
"import": "Import",
"export": "Export",
"search": "Search flows",
"clipboard": "Clipboard",
"library": "Library",
"examples": "Examples",
@ -44,9 +50,10 @@
"add": "Add",
"rename": "Rename",
"delete": "Delete",
"keyboardShortcuts": "Keyboard Shortcuts",
"keyboardShortcuts": "Keyboard shortcuts",
"login": "Login",
"logout": "Logout"
"logout": "Logout",
"editPalette":"Manage palette"
}
},
"user": {
@ -72,16 +79,29 @@
}
},
"clipboard": {
"nodes": "Nodes:",
"nodes": "Nodes",
"selectNodes": "Select the text above and copy to the clipboard.",
"pasteNodes": "Paste nodes here",
"importNodes": "Import nodes",
"exportNodes": "Export nodes to clipboard",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"nodesExported": "Nodes exported to clipboard",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"invalidFlow": "Invalid flow: __message__"
"invalidFlow": "Invalid flow: __message__",
"export": {
"selected":"selected nodes",
"current":"current flow",
"all":"all flows",
"compact":"compact",
"formatted":"formatted",
"copy": "Export to clipboard"
},
"import": {
"import": "Import to",
"newFlow": "new flow"
}
},
"deploy": {
"deploy": "Deploy",
@ -101,12 +121,15 @@
"confirm": {
"button": {
"confirm": "Confirm deploy",
"cancel": "Cancel"
"review": "Review differences",
"cancel": "Cancel",
"merge": "Merge changes"
},
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
"unknown": "The workspace contains some unknown node types:",
"confirm": "Are you sure you want to deploy?"
"confirm": "Are you sure you want to deploy?",
"conflict": "The server is running a more recent set of flows."
}
},
"subflow": {
@ -146,7 +169,7 @@
"addRemoveNode": "Add/remove node from selection",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export selected nodes",
"exportNode": "Export nodes",
"nudgeNode": "Move selected node(s) by a small amount",
"moveNode": "Move selected node(s) by a large amount",
"toggleSidebar": "Toggle sidebar",
@ -154,7 +177,8 @@
"copyNode": "Copy selected nodes",
"cutNode": "Cut selected nodes",
"pasteNode": "Paste nodes",
"undoChange": "Undo the last change performed"
"undoChange": "Undo the last change performed",
"searchBox": "Open search box"
},
"library": {
"openLibrary": "Open Library...",
@ -180,6 +204,7 @@
"palette": {
"noInfo": "no information available",
"filter": "filter nodes",
"search": "search modules",
"label": {
"subflows": "subflows",
"input": "input",
@ -199,6 +224,50 @@
"nodeEnabled_plural": "Nodes enabled:",
"nodeDisabled": "Node disabled:",
"nodeDisabled_plural": "Nodes disabled:"
},
"editor": {
"title": "Manage palette",
"times": {
"seconds": "seconds ago",
"minutes": "minutes ago",
"minutesV": "__count__ minutes ago",
"hoursV": "__count__ hour ago",
"hoursV_plural": "__count__ hours ago",
"daysV": "__count__ day ago",
"daysV_plural": "__count__ days ago",
"weeksV": "__count__ week ago",
"weeksV_plural": "__count__ weeks ago",
"monthsV": "__count__ month ago",
"monthsV_plural": "__count__ months ago",
"yearsV": "__count__ year ago",
"yearsV_plural": "__count__ years ago",
"yearMonthsV": "__y__ year, __count__ month ago",
"yearMonthsV_plural": "__y__ year, __count__ months ago",
"yearsMonthsV": "__y__ years, __count__ month ago",
"yearsMonthsV_plural": "__y__ years, __count__ months ago"
},
"nodeCount": "__label__ node",
"nodeCount_plural": "__label__ nodes",
"inuse": "in use",
"enableall": "enable all",
"disableall": "disable all",
"enable": "enable",
"disable": "disable",
"remove": "remove",
"install": "install",
"installed": "installed",
"loading": "Loading catalogues...",
"tab-nodes": "Nodes",
"tab-install": "Install",
"sort": "sort:",
"sortAZ": "a-z",
"sortRecent": "recent",
"more": "+ __count__ more",
"errors": {
"installFailed": "Failed to install: __module__<br>__message__<br>Check the log for more information"
}
}
},
"sidebar": {
@ -218,13 +287,17 @@
"config": {
"name": "Configuration nodes",
"label": "config",
"global": "Global",
"global": "On all flows",
"none": "none",
"subflows": "subflows",
"flows": "flows",
"filterUnused":"unused",
"filterAll":"all",
"filtered": "__count__ hidden"
},
"palette": {
"name": "Palette management",
"label": "palette"
}
},
"typedInput": {
@ -239,5 +312,8 @@
},
"editableList": {
"add": "add"
},
"search": {
"empty": "No matches found"
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright 2015 IBM Corp.
* Copyright 2015, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -161,6 +161,9 @@ module.exports = {
themeSettings.menu = theme.menu;
}
if (theme.hasOwnProperty("palette")) {
themeSettings.palette = theme.palette;
}
return themeApp;
},
context: function() {

View File

@ -83,7 +83,7 @@ function start() {
.then(function() {
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"locales")),"runtime.json")
})
.then(function() { return storage.init(settings)})
.then(function() { return storage.init(runtime)})
.then(function() { return settings.load(storage)})
.then(function() {

View File

@ -72,6 +72,7 @@
"nodes": {
"credentials": {
"error":"Error loading credentials: __message__",
"error-saving":"Error saving credentials: __message__",
"not-registered": "Credential type '__type__' is not registered"
},
"flows": {

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014, 2015 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,26 +15,141 @@
**/
var when = require("when");
var crypto = require('crypto');
var settings;
var log;
var log = require("../log");
var encryptedCredentials = null;
var credentialCache = {};
var storage = null;
var credentialsDef = {};
var dirty = false;
module.exports = {
init: function (_storage) {
storage = _storage;
var removeDefaultKey = false;
var encryptionEnabled = null;
var encryptionAlgorithm = "aes-256-ctr";
var encryptionKey;
function decryptCredentials(key,credentials) {
var creds = credentials["$"];
var initVector = new Buffer(creds.substring(0, 32),'hex');
creds = creds.substring(32);
var decipher = crypto.createDecipheriv(encryptionAlgorithm, key, initVector);
var decrypted = decipher.update(creds, 'base64', 'utf8') + decipher.final('utf8');
return JSON.parse(decrypted);
}
var api = module.exports = {
init: function(runtime) {
log = runtime.log;
settings = runtime.settings;
dirty = false;
credentialCache = {};
credentialsDef = {};
encryptionEnabled = null;
},
/**
* Loads the credentials from storage.
* Sets the credentials from storage.
*/
load: function () {
return storage.getCredentials().then(function (creds) {
credentialCache = creds;
}).otherwise(function (err) {
log.warn(log._("nodes.credentials.error",{message: err}));
load: function (credentials) {
dirty = false;
/*
- if encryptionEnabled === null, check the current configuration
*/
var credentialsEncrypted = credentials.hasOwnProperty("$") && Object.keys(credentials).length === 1;
var setupEncryptionPromise = when.resolve();
if (encryptionEnabled === null) {
var defaultKey;
try {
defaultKey = settings.get('_credentialSecret');
} catch(err) {
}
if (defaultKey) {
defaultKey = crypto.createHash('sha256').update(defaultKey).digest();
}
var userKey;
try {
userKey = settings.get('credentialSecret');
} catch(err) {
userKey = false;
}
if (userKey === false) {
log.debug("red/runtime/nodes/credentials.load : user disabled encryption");
// User has disabled encryption
encryptionEnabled = false;
// Check if we have a generated _credSecret to decrypt with and remove
if (defaultKey) {
log.debug("red/runtime/nodes/credentials.load : default key present. Will migrate");
if (credentialsEncrypted) {
try {
credentials = decryptCredentials(defaultKey,credentials)
} catch(err) {
credentials = {};
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
}
}
dirty = true;
removeDefaultKey = true;
}
} else if (typeof userKey === 'string') {
log.debug("red/runtime/nodes/credentials.load : user provided key");
// User has provided own encryption key, get the 32-byte hash of it
encryptionKey = crypto.createHash('sha256').update(userKey).digest();
encryptionEnabled = true;
if (defaultKey) {
log.debug("red/runtime/nodes/credentials.load : default key present. Will migrate");
// User has provided their own key, but we already have a default key
// Decrypt using default key
if (credentialsEncrypted) {
try {
credentials = decryptCredentials(defaultKey,credentials)
} catch(err) {
credentials = {};
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
}
}
dirty = true;
removeDefaultKey = true;
}
} else {
log.debug("red/runtime/nodes/credentials.load : no user key present");
// User has not provide their own key
encryptionKey = defaultKey;
encryptionEnabled = true;
if (encryptionKey === undefined) {
log.debug("red/runtime/nodes/credentials.load : no default key present - generating one");
// No user-provided key, no generated key
// Generate a new key
defaultKey = crypto.randomBytes(32).toString('hex');
try {
setupEncryptionPromise = settings.set('_credentialSecret',defaultKey);
encryptionKey = crypto.createHash('sha256').update(defaultKey).digest();
} catch(err) {
log.debug("red/runtime/nodes/credentials.load : settings unavailable - disabling encryption");
// Settings unavailable
encryptionEnabled = false;
encryptionKey = null;
}
dirty = true;
} else {
log.debug("red/runtime/nodes/credentials.load : using default key");
}
}
}
return setupEncryptionPromise.then(function() {
if (credentials.hasOwnProperty("$")) {
// These are encrypted credentials
try {
credentialCache = decryptCredentials(encryptionKey,credentials)
} catch(err) {
credentialCache = {};
dirty = true;
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
}
} else {
credentialCache = credentials;
}
});
},
@ -42,11 +157,12 @@ module.exports = {
* Adds a set of credentials for the given node id.
* @param id the node id for the credentials
* @param creds an object of credential key/value pairs
* @return a promise for the saving of credentials to storage
* @return a promise for backwards compatibility TODO: can this be removed?
*/
add: function (id, creds) {
credentialCache[id] = creds;
return storage.saveCredentials(credentialCache);
dirty = true;
return when.resolve();
},
/**
@ -65,7 +181,7 @@ module.exports = {
*/
delete: function (id) {
delete credentialCache[id];
storage.saveCredentials(credentialCache);
dirty = true;
},
/**
@ -77,6 +193,9 @@ module.exports = {
var existingIds = {};
config.forEach(function(n) {
existingIds[n.id] = true;
if (n.credentials) {
api.extract(n);
}
});
var deletedCredentials = false;
for (var c in credentialCache) {
@ -88,10 +207,9 @@ module.exports = {
}
}
if (deletedCredentials) {
return storage.saveCredentials(credentialCache);
} else {
return when.resolve();
dirty = true;
}
return when.resolve();
},
/**
@ -146,17 +264,10 @@ module.exports = {
}
}
credentialCache[nodeID] = savedCredentials;
dirty = true;
}
},
/**
* Saves the credentials to storage
* @return a promise for the saving of credentials to storage
*/
save: function () {
return storage.saveCredentials(credentialCache);
},
/**
* Gets the credential definition for the given node type
* @param type the node type
@ -164,5 +275,33 @@ module.exports = {
*/
getDefinition: function (type) {
return credentialsDef[type];
},
dirty: function() {
return dirty;
},
export: function() {
var result = credentialCache;
if (dirty && encryptionEnabled) {
try {
log.debug("red/runtime/nodes/credentials.export : encrypting");
var initVector = crypto.randomBytes(16);
var cipher = crypto.createCipheriv(encryptionAlgorithm, encryptionKey, initVector);
result = {"$":initVector.toString('hex') + cipher.update(JSON.stringify(credentialCache), 'utf8', 'base64') + cipher.final('base64')};
} catch(err) {
log.warn(log._("nodes.credentials.error-saving",{message:err.toString()}))
}
}
dirty = false;
if (removeDefaultKey) {
log.debug("red/runtime/nodes/credentials.export : removing unused default key");
return settings.delete('_credentialSecret').then(function() {
removeDefaultKey = false;
return result;
})
} else {
return when.resolve(result);
}
}
}

View File

@ -43,12 +43,12 @@ var subflowInstanceNodeMap = {};
var typeEventRegistered = false;
function init(_settings, _storage) {
function init(runtime) {
if (started) {
throw new Error("Cannot init without a stop");
}
settings = _settings;
storage = _storage;
settings = runtime.settings;
storage = runtime.storage;
started = false;
if (!typeEventRegistered) {
events.on('type-registered',function(type) {
@ -66,64 +66,75 @@ function init(_settings, _storage) {
typeEventRegistered = true;
}
}
function load() {
return storage.getFlows().then(function(flows) {
return credentials.load().then(function() {
return setConfig(flows,"load");
function loadFlows() {
return storage.getFlows().then(function(config) {
return credentials.load(config.credentials).then(function() {
return config;
});
}).otherwise(function(err) {
log.warn(log._("nodes.flows.error",{message:err.toString()}));
console.log(err.stack);
});
}
function load() {
return setFlows(null,"load",false);
}
function setConfig(_config,type,muteLog) {
var config = clone(_config);
/*
* _config - new node array configuration
* type - full/nodes/flows/load (default full)
* muteLog - don't emit the standard log messages (used for individual flow api)
*/
function setFlows(_config,type,muteLog) {
type = type||"full";
var credentialsChanged = false;
var credentialSavePromise = null;
var configSavePromise = null;
var config = null;
var diff;
var newFlowConfig = flowUtil.parseConfig(clone(config));
if (type !== 'full' && type !== 'load') {
var newFlowConfig;
if (type === "load") {
configSavePromise = loadFlows().then(function(_config) {
config = clone(_config.flows);
newFlowConfig = flowUtil.parseConfig(clone(config));
type = "full";
return _config.rev;
});
} else {
config = clone(_config);
newFlowConfig = flowUtil.parseConfig(clone(config));
if (type !== 'full') {
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
}
config.forEach(function(node) {
if (node.credentials) {
credentials.extract(node);
credentialsChanged = true;
credentials.clean(config);
var credsDirty = credentials.dirty();
configSavePromise = credentials.export().then(function(creds) {
var saveConfig = {
flows: config,
credentialsDirty:credsDirty,
credentials: creds
}
});
if (credentialsChanged) {
credentialSavePromise = credentials.save();
} else {
credentialSavePromise = when.resolve();
}
if (type === 'load') {
configSavePromise = credentialSavePromise;
type = 'full';
} else {
configSavePromise = credentialSavePromise.then(function() {
return storage.saveFlows(config);
return storage.saveFlows(saveConfig);
});
}
return configSavePromise
.then(function() {
activeConfig = config;
.then(function(flowRevision) {
activeConfig = {
flows:config,
rev:flowRevision
};
activeFlowConfig = newFlowConfig;
return credentials.clean(activeConfig).then(function() {
if (started) {
return stop(type,diff,muteLog).then(function() {
context.clean(activeFlowConfig);
start(type,diff,muteLog);
return flowRevision;
}).otherwise(function(err) {
})
}
});
});
}
function getNode(id) {
@ -150,7 +161,7 @@ function eachNode(cb) {
}
}
function getConfig() {
function getFlows() {
return activeConfig;
}
@ -348,8 +359,8 @@ function checkTypeInUse(id) {
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
} else {
var inUse = {};
var config = getConfig();
config.forEach(function(n) {
var config = getFlows();
config.flows.forEach(function(n) {
inUse[n.type] = (inUse[n.type]||0)+1;
});
var nodesInUse = [];
@ -425,10 +436,10 @@ function addFlow(flow) {
nodes.push(node);
}
}
var newConfig = clone(activeConfig);
var newConfig = clone(activeConfig.flows);
newConfig = newConfig.concat(nodes);
return setConfig(newConfig,'flows',true).then(function() {
return setFlows(newConfig,'flows',true).then(function() {
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
return flow.id;
});
@ -508,7 +519,7 @@ function updateFlow(id,newFlow) {
}
label = activeFlowConfig.flows[id].label;
}
var newConfig = clone(activeConfig);
var newConfig = clone(activeConfig.flows);
var nodes;
if (id === 'global') {
@ -546,7 +557,7 @@ function updateFlow(id,newFlow) {
}
newConfig = newConfig.concat(nodes);
return setConfig(newConfig,'flows',true).then(function() {
return setFlows(newConfig,'flows',true).then(function() {
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
})
}
@ -563,12 +574,12 @@ function removeFlow(id) {
throw e;
}
var newConfig = clone(activeConfig);
var newConfig = clone(activeConfig.flows);
newConfig = newConfig.filter(function(node) {
return node.z !== id && node.id !== id;
});
return setConfig(newConfig,'flows',true).then(function() {
return setFlows(newConfig,'flows',true).then(function() {
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
});
}
@ -588,7 +599,7 @@ module.exports = {
/**
* Gets the current flow configuration
*/
getFlows: getConfig,
getFlows: getFlows,
/**
* Sets the current active config.
@ -596,7 +607,7 @@ module.exports = {
* @param type the type of deployment to do: full (default), nodes, flows, load
* @return a promise for the saving/starting of the new flow
*/
setFlows: setConfig,
setFlows: setFlows,
/**
* Starts the current flow configuration

View File

@ -77,8 +77,8 @@ function createNode(node,def) {
function init(runtime) {
settings = runtime.settings;
credentials.init(runtime.storage);
flows.init(runtime.settings,runtime.storage);
credentials.init(runtime);
flows.init(runtime);
registry.init(runtime);
context.init(runtime.settings);
}

View File

@ -188,7 +188,8 @@ function loadNodeConfig(fileInfo) {
template: file.replace(/\.js$/,".html"),
enabled: isEnabled,
loaded:false,
version: version
version: version,
local: fileInfo.local
};
if (fileInfo.hasOwnProperty("types")) {
node.types = fileInfo.types;

View File

@ -141,7 +141,8 @@ function scanTreeForNodesModules(moduleName) {
if (settings.userDir) {
userDir = path.join(settings.userDir,"node_modules");
results = results.concat(scanDirForNodesModules(userDir,moduleName));
results = scanDirForNodesModules(userDir,moduleName);
results.forEach(function(r) { r.local = true; });
}
if (dir) {
@ -240,12 +241,14 @@ function getNodeFiles(disableNodePathScan) {
nodeList[moduleFile.package.name] = {
name: moduleFile.package.name,
version: moduleFile.package.version,
local: moduleFile.local||false,
nodes: {}
};
if (moduleFile.package['node-red'].version) {
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
}
nodeModuleFiles.forEach(function(node) {
node.local = moduleFile.local||false;
nodeList[moduleFile.package.name].nodes[node.name] = node;
});
nodeFiles = nodeFiles.concat(nodeModuleFiles);

View File

@ -56,7 +56,8 @@ function filterNodeInfo(n) {
id: n.id||n.module+"/"+n.name,
name: n.name,
types: n.types,
enabled: n.enabled
enabled: n.enabled,
local: n.local||false
};
if (n.hasOwnProperty("module")) {
r.module = n.module;
@ -90,6 +91,7 @@ function saveNodeList() {
moduleList[module] = {
name: module,
version: moduleConfigs[module].version,
local: moduleConfigs[module].local||false,
nodes: {}
};
}
@ -179,6 +181,7 @@ function addNodeSet(id,set,version) {
if (version) {
moduleConfigs[set.module].version = version;
}
moduleConfigs[set.module].local = set.local;
moduleConfigs[set.module].nodes[set.name] = set;
nodeList.push(id);
@ -306,6 +309,7 @@ function getModuleInfo(module) {
var m = {
name: module,
version: moduleConfigs[module].version,
local: moduleConfigs[module].local,
nodes: []
};
for (var i = 0; i < nodes.length; ++i) {

View File

@ -71,6 +71,19 @@ var persistentSettings = {
return storage.saveSettings(globalSettings);
}
},
delete: function(prop) {
if (userSettings.hasOwnProperty(prop)) {
throw new Error(log._("settings.property-read-only", {prop:prop}));
}
if (globalSettings === null) {
throw new Error(log._("settings.not-available"));
}
if (globalSettings.hasOwnProperty(prop)) {
delete globalSettings[prop];
return storage.saveSettings(globalSettings);
}
return when.resolve();
},
available: function() {
return (globalSettings !== null);

View File

@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +16,11 @@
var when = require('when');
var Path = require('path');
var crypto = require('crypto');
var log = require("../log");
var runtime;
var storageModule;
var settingsAvailable;
var sessionsAvailable;
@ -42,28 +45,52 @@ function is_malicious(path) {
}
var storageModuleInterface = {
init: function(settings) {
init: function(_runtime) {
runtime = _runtime;
try {
storageModule = moduleSelector(settings);
storageModule = moduleSelector(runtime.settings);
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
sessionsAvailable = storageModule.hasOwnProperty("getSessions") && storageModule.hasOwnProperty("saveSessions");
} catch (e) {
return when.reject(e);
}
return storageModule.init(settings);
return storageModule.init(runtime.settings);
},
getFlows: function() {
return storageModule.getFlows();
return storageModule.getFlows().then(function(flows) {
return storageModule.getCredentials().then(function(creds) {
var result = {
flows: flows,
credentials: creds
};
result.rev = crypto.createHash('md5').update(JSON.stringify(result)).digest("hex");
return result;
})
});
},
saveFlows: function(flows) {
return storageModule.saveFlows(flows);
},
getCredentials: function() {
return storageModule.getCredentials();
},
saveCredentials: function(credentials) {
return storageModule.saveCredentials(credentials);
saveFlows: function(config) {
var flows = config.flows;
var credentials = config.credentials;
var credentialSavePromise;
if (config.credentialsDirty) {
credentialSavePromise = storageModule.saveCredentials(credentials);
} else {
credentialSavePromise = when.resolve();
}
delete config.credentialsDirty;
return credentialSavePromise.then(function() {
return storageModule.saveFlows(flows).then(function() {
return crypto.createHash('md5').update(JSON.stringify(config)).digest("hex");
})
});
},
// getCredentials: function() {
// return storageModule.getCredentials();
// },
// saveCredentials: function(credentials) {
// return storageModule.saveCredentials(credentials);
// },
getSettings: function() {
if (settingsAvailable) {
return storageModule.getSettings();

View File

@ -54,6 +54,14 @@ module.exports = {
// property to true:
//flowFilePretty: true,
// By default, credentials are encrypted in storage using a generated key. To
// specify your own secret, set the following property.
// If you want to disable encryption of credentials, set this property to false.
// Note: once you set this property, do not change it - doing so will prevent
// node-red from being able to decrypt your existing credentials and they will be
// lost.
//credentialSecret: "a-secret-key",
// By default, all user data is stored in the Node-RED install directory. To
// use a different location, the following property can be used
//userDir: '/home/nol/.node-red/',

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -72,18 +72,8 @@ module.exports = {
var storage = {
getFlows: function() {
var defer = when.defer();
defer.resolve(testFlows);
return defer.promise;
},
getCredentials: function() {
var defer = when.defer();
defer.resolve(testCredentials);
return defer.promise;
},
saveCredentials: function() {
// do nothing
},
return when.resolve({flows:testFlows,credentials:testCredentials});
}
};
var settings = {
@ -102,8 +92,7 @@ module.exports = {
return messageId;
};
redNodes.init({settings:settings, storage:storage});
credentials.init(storage,express());
redNodes.init({settings:settings, storage:storage,log:log});
RED.nodes.registerType("helper", helperNode);
if (Array.isArray(testNode)) {
for (i = 0; i < testNode.length; i++) {
@ -114,7 +103,7 @@ module.exports = {
}
flows.load().then(function() {
flows.startFlows();
should.deepEqual(testFlows, flows.getFlows());
should.deepEqual(testFlows, flows.getFlows().flows);
cb();
});
},

View File

@ -34,12 +34,12 @@ describe("flows api", function() {
app.post("/flows",flows.post);
});
it('returns flow', function(done) {
it('returns flow - v1', function(done) {
flows.init({
settings: {},
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
getFlows: function() { return [1,2,3]; }
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
}
});
request(app)
@ -50,13 +50,60 @@ describe("flows api", function() {
if (err) {
return done(err);
}
res.body.should.be.an.Array;
try {
res.body.should.have.lengthOf(3);
done();
} catch(e) {
return done(e);
}
});
});
it('sets flows - default', function(done) {
it('returns flow - v2', function(done) {
flows.init({
settings: {},
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
}
});
request(app)
.get('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('rev','123');
res.body.should.have.a.property('flows');
res.body.flows.should.have.lengthOf(3);
done();
} catch(e) {
return done(e);
}
});
});
it('returns flow - bad version', function(done) {
request(app)
.get('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','xxx')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('error','bad_api_version');
done();
} catch(e) {
return done(e);
}
});
});
it('sets flows - default - v1', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
@ -77,7 +124,7 @@ describe("flows api", function() {
done();
});
});
it('sets flows - non-default', function(done) {
it('sets flows - non-default - v1', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
@ -100,6 +147,96 @@ describe("flows api", function() {
});
});
it('set flows - rejects mismatched revision - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({rev:456,flows:[4,5,6]})
.expect(409)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("error","version_mismatch");
done();
});
});
it('set flows - rev provided - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve(456);});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({rev:123,flows:[4,5,6]})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("rev",456);
done();
});
});
it('set flows - no rev provided - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve(456);});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({flows:[4,5,6]})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("rev",456);
done();
});
});
it('sets flow - bad version', function(done) {
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','xxx')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('error','bad_api_version');
done();
} catch(e) {
return done(e);
}
});
});
it('reloads flows', function(done) {
var loadFlows = sinon.spy(function() { return when.resolve(); });
flows.init({

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014, 2015 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,34 +19,30 @@ var sinon = require("sinon");
var when = require("when");
var util = require("util");
var express = require("express");
var request = require("supertest");
var index = require("../../../../red/runtime/nodes/index");
var credentials = require("../../../../red/runtime/nodes/credentials");
var log = require("../../../../red/runtime/log");
var auth = require("../../../../red/api/auth");
describe('Credentials', function() {
describe('red/runtime/nodes/credentials', function() {
var encryptionDisabledSettings = {
get: function(key) {
return false;
}
}
afterEach(function() {
index.clearRegistry();
});
it('loads from storage',function(done) {
var storage = {
getCredentials: function() {
return when.promise(function(resolve,reject) {
resolve({"a":{"b":1,"c":2}});
it('loads provided credentials',function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
}
};
credentials.init(storage);
credentials.load().then(function() {
credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.get("a").should.have.property('b',1);
credentials.get("a").should.have.property('c',2);
@ -54,182 +50,120 @@ describe('Credentials', function() {
done();
});
});
it('saves to storage', function(done) {
var storage = {
saveCredentials: function(creds) {
return when.resolve("saveCalled");
}
};
credentials.init(storage);
credentials.save().then(function(res) {
res.should.equal("saveCalled");
it('adds a new credential',function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.dirty().should.be.false();
should.not.exist(credentials.get("b"));
credentials.add("b",{"foo":"bar"}).then(function() {
credentials.get("b").should.have.property("foo","bar");
credentials.dirty().should.be.true();
done();
});
});
it('saves to storage when new cred added', function(done) {
var storage = {
getCredentials: function() {
return when.promise(function(resolve,reject) {
resolve({"a":{"b":1,"c":2}});
});
},
saveCredentials: function(creds) {
return when(true);
}
};
sinon.spy(storage,"saveCredentials");
credentials.init(storage);
credentials.load().then(function() {
should.not.exist(credentials.get("b"))
credentials.add('b',{"d":3});
storage.saveCredentials.callCount.should.be.exactly(1);
credentials.get("b").should.have.property('d',3);
storage.saveCredentials.restore();
done();
it('deletes an existing credential',function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
});
it('deletes from storage', function(done) {
var storage = {
getCredentials: function() {
return when.promise(function(resolve,reject) {
resolve({"a":{"b":1,"c":2}});
});
},
saveCredentials: function(creds) {
return when(true);
}
};
sinon.spy(storage,"saveCredentials");
credentials.init(storage);
credentials.load().then(function() {
should.exist(credentials.get("a"))
credentials.delete('a');
storage.saveCredentials.callCount.should.be.exactly(1);
credentials.load({"a":{"b":1,"c":2}}).then(function() {
credentials.dirty().should.be.false();
credentials.delete("a");
should.not.exist(credentials.get("a"));
storage.saveCredentials.restore();
credentials.dirty().should.be.true();
done();
});
});
it('clean up from storage', function(done) {
var storage = {
getCredentials: function() {
return when.promise(function(resolve,reject) {
resolve({"a":{"b":1,"c":2}});
it('exports the credentials, clearing dirty flag', function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
},
saveCredentials: function(creds) {
return when(true);
}
};
sinon.spy(storage,"saveCredentials");
credentials.init(storage);
credentials.load().then(function() {
var creds = {"a":{"b":1,"c":2}};
credentials.load(creds).then(function() {
credentials.add("b",{"foo":"bar"}).then(function() {
credentials.dirty().should.be.true();
credentials.export().then(function(exported) {
exported.should.eql(creds);
credentials.dirty().should.be.false();
done();
})
});
});
})
describe("#clean",function() {
it("removes credentials of unknown nodes",function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
var creds = {"a":{"b":1,"c":2},"b":{"d":3}};
credentials.load(creds).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("a"));
credentials.clean([]);
storage.saveCredentials.callCount.should.be.exactly(1);
should.exist(credentials.get("b"));
credentials.clean([{id:"b"}]).then(function() {
credentials.dirty().should.be.true();
should.not.exist(credentials.get("a"));
storage.saveCredentials.restore();
should.exist(credentials.get("b"));
done();
});
});
it('handle error loading from storage', function(done) {
var storage = {
getCredentials: function() {
return when.promise(function(resolve,reject) {
reject("test forcing failure");
});
},
saveCredentials: function(creds) {
return when(true);
}
};
var logmsg = 'nothing logged yet';
sinon.stub(log, 'warn', function(msg) {
logmsg = msg;
it("extracts credentials of known nodes",function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.register("testNode",{"b":"text","c":"password"})
var creds = {"a":{"b":1,"c":2}};
var newConfig = [{id:"a",type:"testNode",credentials:{"b":"newBValue","c":"newCValue"}}];
credentials.load(creds).then(function() {
credentials.dirty().should.be.false();
credentials.clean(newConfig).then(function() {
credentials.dirty().should.be.true();
credentials.get("a").should.have.property('b',"newBValue");
credentials.get("a").should.have.property('c',"newCValue");
should.not.exist(newConfig[0].credentials);
done();
});
});
});
credentials.init(storage);
credentials.load().then(function() {
log.warn.calledOnce.should.be.true;
});
it('warns if a node has no credential definition', function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({}).then(function() {
var node = {id:"node",type:"test",credentials:{
user1:"newUser",
password1:"newPassword"
}};
sinon.spy(log,"warn");
credentials.extract(node);
log.warn.called.should.be.true();
should.not.exist(node.credentials);
log.warn.restore();
done();
}).otherwise(function(err){
log.warn.restore();
done(err);
});
});
})
it('credential type is not registered when extract', function(done) {
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
var storage = {
getFlows: function() {
var defer = when.defer();
defer.resolve(testFlows);
return defer.promise;
},
getCredentials: function() {
return when.promise(function(resolve,reject) {
resolve({"tab1":{"b":1,"c":2}});
it('extract credential updates in the provided node', function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
},
saveFlows: function(conf) {
var defer = when.defer();
defer.resolve();
should.deepEqual(testFlows, conf);
return defer.promise;
},
saveCredentials: function(creds) {
return when(true);
},
getSettings: function() {
return when({});
},
saveSettings: function(s) {
return when();
}
};
function TestNode(n) {
index.createNode(this, n);
this.id = 'tab1';
this.type = 'test';
this.name = 'barney';
var node = this;
this.on("log", function() {
// do nothing
});
}
var logmsg = 'nothing logged yet';
sinon.stub(log, 'warn', function(msg) {
logmsg = msg;
});
var settings = {
available: function() { return false;}
}
index.init({settings:settings, storage:storage});
index.registerType('test', TestNode);
index.loadFlows().then(function() {
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
credentials.extract(testnode);
log.warn.calledOnce.should.be.true;
log.warn.restore();
done();
}).otherwise(function(err){
log.warn.restore();
done(err);
});
});
it('extract and store credential updates in the provided node', function(done) {
credentials.init({saveCredentials:function(){}},express());
credentials.register("test",{
var defintion = {
user1:{type:"text"},
password1:{type:"password"},
user2:{type:"text"},
@ -237,8 +171,12 @@ describe('Credentials', function() {
user3:{type:"text"},
password3:{type:"password"}
});
credentials.add("node",{user1:"abc",password1:"123",user2:"def",password2:"456",user3:"ghi",password3:"789"});
};
credentials.register("test",defintion);
var def = credentials.getDefinition("test");
defintion.should.eql(def);
credentials.load({"node":{user1:"abc",password1:"123",user2:"def",password2:"456",user3:"ghi",password3:"789"}}).then(function() {
var node = {id:"node",type:"test",credentials:{
// user1 unchanged
password1:"__PWRD__",
@ -247,10 +185,12 @@ describe('Credentials', function() {
user3:"newUser",
password3:"newPassword"
}};
credentials.dirty().should.be.false();
credentials.extract(node);
node.should.not.have.a.property("credentials");
credentials.dirty().should.be.true();
var newCreds = credentials.get("node");
newCreds.should.have.a.property("user1","abc");
newCreds.should.have.a.property("password1","123");
@ -261,4 +201,271 @@ describe('Credentials', function() {
done();
});
});
it('extract ignores node without credentials', function(done) {
credentials.init({
log: log,
settings: encryptionDisabledSettings
});
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
var node = {id:"node",type:"test"};
credentials.dirty().should.be.false();
credentials.extract(node);
credentials.dirty().should.be.false();
done();
});
});
describe("encryption",function() {
var settings = {};
var runtime = {
log: log,
settings: {
get: function(key) {
return settings[key];
},
set: function(key,value) {
settings[key] = value;
return when.resolve();
},
delete: function(key) {
delete settings[key];
return when.resolve();
}
}
}
it('migrates to encrypted and generates default key', function(done) {
settings = {};
credentials.init(runtime);
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
settings.should.have.a.property("_credentialSecret");
settings._credentialSecret.should.have.a.length(64);
credentials.dirty().should.be.true();
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
done();
})
});
});
});
it('uses default key', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
should.exist(credentials.get("node"));
credentials.dirty().should.be.false();
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('uses user key', function(done) {
settings = {
credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("node"));
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('uses user key - when settings are otherwise unavailable', function(done) {
var runtime = {
log: log,
settings: {
get: function(key) {
if (key === 'credentialSecret') {
return "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a";
}
throw new Error();
},
set: function(key,value) {
throw new Error();
}
}
}
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
should.exist(credentials.get("node"));
credentials.add("node",{user1:"def",password1:"456"});
credentials.export().then(function(result) {
result.should.have.a.property("$");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","def");
credentials.get("node").should.have.a.property("password1","456");
done();
})
});
});
});
it('migrates from default key to user key', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","abc");
credentials.get("node").should.have.a.property("password1","123");
done();
})
});
});
});
it('migrates from default key to user key - unencrypted original', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
};
// {"node":{user1:"abc",password1:"123"}}
var unencryptedFlows = {"node":{user1:"abc",password1:"123"}};
credentials.init(runtime);
credentials.load(unencryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
credentials.load(result).then(function() {
should.exist(credentials.get("node"));
credentials.get("node").should.have.a.property("user1","abc");
credentials.get("node").should.have.a.property("password1","123");
done();
})
});
});
});
it('migrates from default key to unencrypted', function(done) {
settings = {
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
credentialSecret: false
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.not.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
result.should.eql({"node":{user1:"abc",password1:"123"}});
done();
});
});
});
it('handles bad default key - resets credentials', function(done) {
settings = {
_credentialSecret: "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.not.exist(credentials.get("node"));
done();
});
});
it('handles bad user key - resets credentials', function(done) {
settings = {
credentialSecret: "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
credentials.dirty().should.be.true();
should.not.exist(credentials.get("node"));
done();
});
});
it('handles unavailable settings - leaves creds unencrypted', function(done) {
var runtime = {
log: log,
settings: {
get: function(key) {
throw new Error();
},
set: function(key,value) {
throw new Error();
}
}
}
// {"node":{user1:"abc",password1:"123"}}
credentials.init(runtime);
credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
credentials.dirty().should.be.false();
should.exist(credentials.get("node"));
credentials.export().then(function(result) {
result.should.not.have.a.property("$");
result.should.have.a.property("node");
done();
});
});
});
})
})

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014, 2015 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,8 +30,6 @@ describe('flows/index', function() {
var storage;
var eventsOn;
var credentialsExtract;
var credentialsSave;
var credentialsClean;
var credentialsLoad;
@ -50,13 +48,10 @@ describe('flows/index', function() {
beforeEach(function() {
eventsOn = sinon.spy(events,"on");
credentialsExtract = sinon.stub(credentials,"extract",function(conf) {
delete conf.credentials;
});
credentialsSave = sinon.stub(credentials,"save",function() {
return when.resolve();
});
credentialsClean = sinon.stub(credentials,"clean",function(conf) {
conf.forEach(function(n) {
delete n.credentials;
});
return when.resolve();
});
credentialsLoad = sinon.stub(credentials,"load",function() {
@ -97,8 +92,6 @@ describe('flows/index', function() {
afterEach(function(done) {
eventsOn.restore();
credentialsExtract.restore();
credentialsSave.restore();
credentialsClean.restore();
credentialsLoad.restore();
flowCreate.restore();
@ -119,28 +112,35 @@ describe('flows/index', function() {
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
credentialsExtract.called.should.be.false;
credentialsClean.called.should.be.true;
storage.hasOwnProperty('conf').should.be.true;
flows.getFlows().should.eql(originalConfig);
flows.getFlows().flows.should.eql(originalConfig);
done();
});
});
it('sets the full flow for type load', function(done) {
it('loads the full flow for type load', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({},storage);
var loadStorage = {
saveFlows: function(conf) {
loadStorage.conf = conf;
return when.resolve(456);
},
getFlows: function() {
return when.resolve({flows:originalConfig,rev:123})
}
}
flows.init({settings:{},storage:loadStorage});
flows.setFlows(originalConfig,"load").then(function() {
credentialsExtract.called.should.be.false;
credentialsClean.called.should.be.true;
credentialsClean.called.should.be.false;
// 'load' type does not trigger a save
storage.hasOwnProperty('conf').should.be.false;
flows.getFlows().should.eql(originalConfig);
loadStorage.hasOwnProperty('conf').should.be.false;
flows.getFlows().flows.should.eql(originalConfig);
done();
});
@ -148,19 +148,18 @@ describe('flows/index', function() {
it('extracts credentials from the full flow', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[],credentials:{}},
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[],credentials:{"a":1}},
{id:"t1",type:"tab"}
];
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
credentialsExtract.called.should.be.true;
credentialsClean.called.should.be.true;
storage.hasOwnProperty('conf').should.be.true;
var cleanedFlows = flows.getFlows();
storage.conf.should.eql(cleanedFlows);
cleanedFlows.should.not.eql(originalConfig);
cleanedFlows[0].credentials = {};
cleanedFlows.should.eql(originalConfig);
storage.conf.flows.should.eql(cleanedFlows.flows);
cleanedFlows.flows.should.not.eql(originalConfig);
cleanedFlows.flows[0].credentials = {"a":1};
cleanedFlows.flows.should.eql(originalConfig);
done();
});
});
@ -175,12 +174,12 @@ describe('flows/index', function() {
newConfig.push({id:"t2",type:"tab"});
newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]});
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
flows.setFlows(newConfig,"nodes").then(function() {
flows.getFlows().should.eql(newConfig);
flows.getFlows().flows.should.eql(newConfig);
flowCreate.flows['t1'].update.called.should.be.true;
flowCreate.flows['t2'].start.called.should.be.true;
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
@ -188,7 +187,7 @@ describe('flows/index', function() {
})
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -204,12 +203,12 @@ describe('flows/index', function() {
newConfig.push({id:"t2",type:"tab"});
newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]});
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
flows.setFlows(newConfig,"nodes").then(function() {
flows.getFlows().should.eql(newConfig);
flows.getFlows().flows.should.eql(newConfig);
flowCreate.flows['t1'].update.called.should.be.true;
flowCreate.flows['t2'].start.called.should.be.true;
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
@ -217,7 +216,7 @@ describe('flows/index', function() {
})
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -232,16 +231,14 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
credentialsExtract.called.should.be.false;
credentialsLoad.called.should.be.true;
credentialsClean.called.should.be.true;
// 'load' type does not trigger a save
storage.hasOwnProperty('conf').should.be.false;
flows.getFlows().should.eql(originalConfig);
flows.getFlows().flows.should.eql(originalConfig);
done();
});
});
@ -254,7 +251,7 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
@ -262,7 +259,7 @@ describe('flows/index', function() {
done();
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -274,10 +271,10 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
flowCreate.called.should.be.false;
@ -293,9 +290,9 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
flowCreate.called.should.be.false;
@ -316,7 +313,7 @@ describe('flows/index', function() {
});
describe('#get',function() {
describe.skip('#get',function() {
});
@ -327,9 +324,9 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
var c = 0;
flows.eachNode(function(node) {
@ -351,7 +348,7 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
@ -360,7 +357,7 @@ describe('flows/index', function() {
done();
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -376,7 +373,7 @@ describe('flows/index', function() {
{id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
@ -391,7 +388,7 @@ describe('flows/index', function() {
}
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -404,7 +401,7 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
@ -413,7 +410,7 @@ describe('flows/index', function() {
done();
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -430,7 +427,7 @@ describe('flows/index', function() {
{id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
events.once('nodes-started',function() {
@ -445,7 +442,7 @@ describe('flows/index', function() {
}
});
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.startFlows();
});
@ -473,7 +470,7 @@ describe('flows/index', function() {
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
flows.checkTypeInUse("unused-module");
done();
@ -484,7 +481,7 @@ describe('flows/index', function() {
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t1",type:"tab"}
];
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.setFlows(originalConfig).then(function() {
/*jshint immed: false */
try {
@ -505,9 +502,9 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
flows.addFlow({
label:'new flow',
@ -529,12 +526,12 @@ describe('flows/index', function() {
{id:"t1",type:"tab"}
];
storage.getFlows = function() {
return when.resolve(originalConfig);
return when.resolve({flows:originalConfig});
}
storage.setFlows = function() {
return when.resolve();
}
flows.init({},storage);
flows.init({settings:{},storage:storage});
flows.load().then(function() {
return flows.startFlows();
}).then(function() {
@ -547,7 +544,7 @@ describe('flows/index', function() {
{id:"t2-3",z:"t1",type:"test"}
]
}).then(function(id) {
flows.getFlows().should.have.lengthOf(6);
flows.getFlows().flows.should.have.lengthOf(6);
var createdFlows = Object.keys(flowCreate.flows);
createdFlows.should.have.lengthOf(3);
createdFlows[2].should.eql(id);

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,29 +39,26 @@ describe("red/nodes/index", function() {
});
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
var testCredentials = {"tab1":{"b":1,"c":2}};
var storage = {
getFlows: function() {
return when(testFlows);
},
getCredentials: function() {
return when({"tab1":{"b":1,"c":2}});
return when({red:123,flows:testFlows,credentials:testCredentials});
},
saveFlows: function(conf) {
should.deepEqual(testFlows, conf);
return when();
},
saveCredentials: function(creds) {
return when(true);
should.deepEqual(testFlows, conf.flows);
return when.resolve(123);
}
};
var settings = {
available: function() { return false }
available: function() { return false },
get: function() { return false }
};
var runtime = {
settings: settings,
storage: storage
storage: storage,
log: {debug:function(){},warn:function(){}}
};
function TestNode(n) {
@ -88,7 +85,9 @@ describe("red/nodes/index", function() {
it('flows should be initialised',function(done) {
index.init(runtime);
index.loadFlows().then(function() {
should.deepEqual(testFlows, index.getFlows());
console.log(testFlows);
console.log(index.getFlows());
should.deepEqual(testFlows, index.getFlows().flows);
done();
}).otherwise(function(err) {
done(err);
@ -177,8 +176,8 @@ describe("red/nodes/index", function() {
index.registerType('test', TestNode);
index.loadFlows().then(function() {
var info = index.disableNode("5678");
registry.disableNode.calledOnce.should.be.true;
registry.disableNode.calledWith("5678").should.be.true;
registry.disableNode.calledOnce.should.be.true();
registry.disableNode.calledWith("5678").should.be.true();
info.should.eql(randomNodeInfo);
done();
}).otherwise(function(err) {

View File

@ -1,5 +1,5 @@
/**
* Copyright 2014, 2015 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,7 +22,9 @@ describe("red/storage/index", function() {
it('rejects the promise when settings suggest loading a bad module', function(done) {
var wrongModule = {
settings:{
storageModule : "thisaintloading"
}
};
storage.init(wrongModule).then( function() {
@ -48,7 +50,9 @@ describe("red/storage/index", function() {
};
var setsBooleanModule = {
settings: {
storageModule : moduleWithBooleanSettingInit
}
};
storage.init(setsBooleanModule);
@ -71,12 +75,15 @@ describe("red/storage/index", function() {
},
getFlows : function() {
calledFlagGetFlows = true;
return when.resolve([]);
},
saveFlows : function (flows) {
flows.should.be.true;
return when.resolve("");
},
getCredentials : function() {
calledFlagGetCredentials = true;
return when.resolve({});
},
saveCredentials : function(credentials) {
credentials.should.be.true;
@ -116,14 +123,14 @@ describe("red/storage/index", function() {
};
var moduleToLoad = {
settings: {
storageModule : interfaceCheckerModule
}
};
storage.init(moduleToLoad);
storage.getFlows();
storage.saveFlows(true);
storage.getCredentials();
storage.saveCredentials(true);
storage.saveFlows({flows:[],credentials:{}});
storage.getSettings();
storage.saveSettings(true);
storage.getSessions();
@ -172,7 +179,9 @@ describe("red/storage/index", function() {
};
var moduleToLoad = {
settings: {
storageModule : interfaceCheckerModule
}
};
before(function() {
storage.init(moduleToLoad);
@ -220,7 +229,7 @@ describe("red/storage/index", function() {
var interfaceCheckerModule = {
init : function () {}
};
storage.init({storageModule: interfaceCheckerModule});
storage.init({settings:{storageModule: interfaceCheckerModule}});
});
it('defaults missing getSettings',function(done) {