mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
commit
7822ab113a
16
Gruntfile.js
16
Gruntfile.js
@ -52,10 +52,12 @@ module.exports = function(grunt) {
|
|||||||
timeout: 3000,
|
timeout: 3000,
|
||||||
ignoreLeaks: false,
|
ignoreLeaks: false,
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
reportFormats: ['lcov'],
|
reportFormats: ['lcov','html'],
|
||||||
print: 'both'
|
print: 'both'
|
||||||
},
|
},
|
||||||
coverage: { src: ['test/**/*_spec.js'] }
|
all: { src: ['test/**/*_spec.js'] },
|
||||||
|
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
|
||||||
|
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||||
},
|
},
|
||||||
jshint: {
|
jshint: {
|
||||||
options: {
|
options: {
|
||||||
@ -156,6 +158,10 @@ module.exports = function(grunt) {
|
|||||||
"editor/js/ui/typeSearch.js",
|
"editor/js/ui/typeSearch.js",
|
||||||
"editor/js/ui/subflow.js",
|
"editor/js/ui/subflow.js",
|
||||||
"editor/js/ui/userSettings.js",
|
"editor/js/ui/userSettings.js",
|
||||||
|
"editor/js/ui/projects/projects.js",
|
||||||
|
"editor/js/ui/projects/projectSettings.js",
|
||||||
|
"editor/js/ui/projects/projectUserSettings.js",
|
||||||
|
"editor/js/ui/projects/tab-versionControl.js",
|
||||||
"editor/js/ui/touch/radialMenu.js"
|
"editor/js/ui/touch/radialMenu.js"
|
||||||
],
|
],
|
||||||
dest: "public/red/red.js"
|
dest: "public/red/red.js"
|
||||||
@ -472,7 +478,7 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
grunt.registerTask('test-core',
|
grunt.registerTask('test-core',
|
||||||
'Runs code style check and unit tests on core runtime code',
|
'Runs code style check and unit tests on core runtime code',
|
||||||
['jshint:core','simplemocha:core']);
|
['build','mocha_istanbul:core']);
|
||||||
|
|
||||||
grunt.registerTask('test-editor',
|
grunt.registerTask('test-editor',
|
||||||
'Runs code style check on editor code',
|
'Runs code style check on editor code',
|
||||||
@ -484,7 +490,7 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
grunt.registerTask('test-nodes',
|
grunt.registerTask('test-nodes',
|
||||||
'Runs unit tests on core nodes',
|
'Runs unit tests on core nodes',
|
||||||
['simplemocha:nodes']);
|
['build','mocha_istanbul:nodes']);
|
||||||
|
|
||||||
grunt.registerTask('build',
|
grunt.registerTask('build',
|
||||||
'Builds editor content',
|
'Builds editor content',
|
||||||
@ -500,5 +506,5 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
grunt.registerTask('coverage',
|
grunt.registerTask('coverage',
|
||||||
'Run Istanbul code test coverage task',
|
'Run Istanbul code test coverage task',
|
||||||
['build','mocha_istanbul']);
|
['build','mocha_istanbul:all']);
|
||||||
};
|
};
|
||||||
|
@ -321,6 +321,9 @@ RED.history = (function() {
|
|||||||
},
|
},
|
||||||
peek: function() {
|
peek: function() {
|
||||||
return undo_history[undo_history.length-1];
|
return undo_history[undo_history.length-1];
|
||||||
|
},
|
||||||
|
clear: function() {
|
||||||
|
undo_history = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,11 @@
|
|||||||
"ctrl-e": "core:show-export-dialog",
|
"ctrl-e": "core:show-export-dialog",
|
||||||
"ctrl-i": "core:show-import-dialog",
|
"ctrl-i": "core:show-import-dialog",
|
||||||
"ctrl-space": "core:toggle-sidebar",
|
"ctrl-space": "core:toggle-sidebar",
|
||||||
"ctrl-,": "core:show-user-settings"
|
"ctrl-,": "core:show-user-settings",
|
||||||
|
|
||||||
|
"ctrl-alt-n": "core:new-project",
|
||||||
|
"ctrl-alt-o": "core:open-project",
|
||||||
|
"ctrl-g v": "core:show-version-control-tab"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"backspace": "core:delete-selection",
|
"backspace": "core:delete-selection",
|
||||||
|
@ -60,12 +60,32 @@
|
|||||||
$("#palette > .palette-spinner").hide();
|
$("#palette > .palette-spinner").hide();
|
||||||
$(".palette-scroll").removeClass("hide");
|
$(".palette-scroll").removeClass("hide");
|
||||||
$("#palette-search").removeClass("hide");
|
$("#palette-search").removeClass("hide");
|
||||||
loadFlows();
|
loadFlows(function() {
|
||||||
|
if (RED.settings.theme("projects.enabled",false)) {
|
||||||
|
RED.projects.refresh(function(activeProject) {
|
||||||
|
RED.sidebar.info.refresh()
|
||||||
|
if (!activeProject) {
|
||||||
|
// Projects enabled but no active project
|
||||||
|
RED.menu.setDisabled('menu-item-projects-open',true);
|
||||||
|
if (activeProject === false) {
|
||||||
|
// User previously decline the migration to projects.
|
||||||
|
} else { // null/undefined
|
||||||
|
RED.projects.showStartup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completeLoad();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Projects disabled by the user
|
||||||
|
RED.sidebar.info.refresh()
|
||||||
|
completeLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFlows() {
|
function loadFlows(done) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
headers: {
|
headers: {
|
||||||
"Accept":"application/json",
|
"Accept":"application/json",
|
||||||
@ -73,116 +93,218 @@
|
|||||||
cache: false,
|
cache: false,
|
||||||
url: 'flows',
|
url: 'flows',
|
||||||
success: function(nodes) {
|
success: function(nodes) {
|
||||||
var currentHash = window.location.hash;
|
if (nodes) {
|
||||||
RED.nodes.version(nodes.rev);
|
var currentHash = window.location.hash;
|
||||||
RED.nodes.import(nodes.flows);
|
RED.nodes.version(nodes.rev);
|
||||||
RED.nodes.dirty(false);
|
RED.nodes.import(nodes.flows);
|
||||||
RED.view.redraw(true);
|
RED.nodes.dirty(false);
|
||||||
if (/^#flow\/.+$/.test(currentHash)) {
|
RED.view.redraw(true);
|
||||||
RED.workspaces.show(currentHash.substring(6));
|
if (/^#flow\/.+$/.test(currentHash)) {
|
||||||
|
RED.workspaces.show(currentHash.substring(6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
var persistentNotifications = {};
|
|
||||||
RED.comms.subscribe("notification/#",function(topic,msg) {
|
|
||||||
var parts = topic.split("/");
|
|
||||||
var notificationId = parts[1];
|
|
||||||
if (notificationId === "runtime-deploy") {
|
|
||||||
// handled in ui/deploy.js
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (notificationId === "node") {
|
|
||||||
// handled below
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg.text) {
|
|
||||||
var text = RED._(msg.text,{default:msg.text});
|
|
||||||
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
|
||||||
persistentNotifications[notificationId] = RED.notify(text,msg.type,msg.timeout === undefined,msg.timeout);
|
|
||||||
} else {
|
|
||||||
persistentNotifications[notificationId].update(text,msg.timeout);
|
|
||||||
}
|
|
||||||
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
|
||||||
persistentNotifications[notificationId].close();
|
|
||||||
delete persistentNotifications[notificationId];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
RED.comms.subscribe("status/#",function(topic,msg) {
|
|
||||||
var parts = topic.split("/");
|
|
||||||
var node = RED.nodes.node(parts[1]);
|
|
||||||
if (node) {
|
|
||||||
if (msg.hasOwnProperty("text")) {
|
|
||||||
if (msg.text[0] !== ".") {
|
|
||||||
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.status = msg;
|
|
||||||
node.dirty = true;
|
|
||||||
RED.view.redraw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
|
||||||
var i,m;
|
|
||||||
var typeList;
|
|
||||||
var info;
|
|
||||||
if (topic == "notification/node/added") {
|
|
||||||
var addedTypes = [];
|
|
||||||
msg.forEach(function(m) {
|
|
||||||
var id = m.id;
|
|
||||||
RED.nodes.addNodeSet(m);
|
|
||||||
addedTypes = addedTypes.concat(m.types);
|
|
||||||
RED.i18n.loadCatalog(id, function() {
|
|
||||||
$.get('nodes/'+id, function(data) {
|
|
||||||
$("body").append(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (addedTypes.length) {
|
|
||||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
|
||||||
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
|
||||||
}
|
|
||||||
loadIconList();
|
|
||||||
} else if (topic == "notification/node/removed") {
|
|
||||||
for (i=0;i<msg.length;i++) {
|
|
||||||
m = msg[i];
|
|
||||||
info = RED.nodes.removeNodeSet(m.id);
|
|
||||||
if (info.added) {
|
|
||||||
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
|
||||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadIconList();
|
|
||||||
} else if (topic == "notification/node/enabled") {
|
|
||||||
if (msg.types) {
|
|
||||||
info = RED.nodes.getNodeSet(msg.id);
|
|
||||||
if (info.added) {
|
|
||||||
RED.nodes.enableNodeSet(msg.id);
|
|
||||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
|
||||||
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
|
||||||
} else {
|
|
||||||
$.get('nodes/'+msg.id, function(data) {
|
|
||||||
$("body").append(data);
|
|
||||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
|
||||||
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (topic == "notification/node/disabled") {
|
|
||||||
if (msg.types) {
|
|
||||||
RED.nodes.disableNodeSet(msg.id);
|
|
||||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
|
||||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
|
||||||
}
|
|
||||||
} else if (topic == "node/upgraded") {
|
|
||||||
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
|
|
||||||
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
|
|
||||||
}
|
|
||||||
// Refresh flow library to ensure any examples are updated
|
|
||||||
RED.library.loadFlowLibrary();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function completeLoad() {
|
||||||
|
var persistentNotifications = {};
|
||||||
|
RED.comms.subscribe("notification/#",function(topic,msg) {
|
||||||
|
var parts = topic.split("/");
|
||||||
|
var notificationId = parts[1];
|
||||||
|
if (notificationId === "runtime-deploy") {
|
||||||
|
// handled in ui/deploy.js
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (notificationId === "node") {
|
||||||
|
// handled below
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (notificationId === "project-update") {
|
||||||
|
RED.nodes.clear();
|
||||||
|
RED.history.clear();
|
||||||
|
RED.view.redraw(true);
|
||||||
|
RED.projects.refresh(function() {
|
||||||
|
loadFlows(function() {
|
||||||
|
var project = RED.projects.getActiveProject();
|
||||||
|
var message = {
|
||||||
|
"change-branch":"Change to local branch '"+project.git.branches.local+"'",
|
||||||
|
"abort-merge":"Git merge aborted",
|
||||||
|
"loaded":"Project '"+msg.project+"' loaded",
|
||||||
|
"updated":"Project '"+msg.project+"' updated",
|
||||||
|
"pull":"Project '"+msg.project+"' reloaded",
|
||||||
|
"revert": "Project '"+msg.project+"' reloaded"
|
||||||
|
}[msg.action];
|
||||||
|
RED.notify("<p>"+message+"</p>");
|
||||||
|
RED.sidebar.info.refresh()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.text) {
|
||||||
|
msg.default = msg.text;
|
||||||
|
var text = RED._(msg.text,msg);
|
||||||
|
var options = {
|
||||||
|
type: msg.type,
|
||||||
|
fixed: msg.timeout === undefined,
|
||||||
|
timeout: msg.timeout,
|
||||||
|
id: notificationId
|
||||||
|
}
|
||||||
|
if (notificationId === "runtime-state") {
|
||||||
|
if (msg.error === "missing-types") {
|
||||||
|
text+="<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
if (!!RED.projects.getActiveProject()) {
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: "Manage project dependencies",
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
RED.projects.settings.show('deps');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
// } else if (RED.settings.theme('palette.editable') !== false) {
|
||||||
|
} else {
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: "Close",
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else if (msg.error === "credentials_load_failed") {
|
||||||
|
if (RED.user.hasPermission("projects.write")) {
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: "Setup credentials",
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
RED.projects.showCredentialsPrompt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else if (msg.error === "missing_flow_file") {
|
||||||
|
if (RED.user.hasPermission("projects.write")) {
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: "Setup project files",
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
RED.projects.showFilesPrompt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else if (msg.error === "project_empty") {
|
||||||
|
if (RED.user.hasPermission("projects.write")) {
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: "No thanks",
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Create default project files",
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
RED.projects.createDefaultFileSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
||||||
|
persistentNotifications[notificationId] = RED.notify(text,options);
|
||||||
|
} else {
|
||||||
|
persistentNotifications[notificationId].update(text,options);
|
||||||
|
}
|
||||||
|
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
||||||
|
persistentNotifications[notificationId].close();
|
||||||
|
delete persistentNotifications[notificationId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||||
|
var parts = topic.split("/");
|
||||||
|
var node = RED.nodes.node(parts[1]);
|
||||||
|
if (node) {
|
||||||
|
if (msg.hasOwnProperty("text")) {
|
||||||
|
if (msg.text[0] !== ".") {
|
||||||
|
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.status = msg;
|
||||||
|
node.dirty = true;
|
||||||
|
RED.view.redraw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||||
|
var i,m;
|
||||||
|
var typeList;
|
||||||
|
var info;
|
||||||
|
if (topic == "notification/node/added") {
|
||||||
|
var addedTypes = [];
|
||||||
|
msg.forEach(function(m) {
|
||||||
|
var id = m.id;
|
||||||
|
RED.nodes.addNodeSet(m);
|
||||||
|
addedTypes = addedTypes.concat(m.types);
|
||||||
|
RED.i18n.loadCatalog(id, function() {
|
||||||
|
$.get('nodes/'+id, function(data) {
|
||||||
|
$("body").append(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (addedTypes.length) {
|
||||||
|
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
||||||
|
}
|
||||||
|
loadIconList();
|
||||||
|
} else if (topic == "notification/node/removed") {
|
||||||
|
for (i=0;i<msg.length;i++) {
|
||||||
|
m = msg[i];
|
||||||
|
info = RED.nodes.removeNodeSet(m.id);
|
||||||
|
if (info.added) {
|
||||||
|
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadIconList();
|
||||||
|
} else if (topic == "notification/node/enabled") {
|
||||||
|
if (msg.types) {
|
||||||
|
info = RED.nodes.getNodeSet(msg.id);
|
||||||
|
if (info.added) {
|
||||||
|
RED.nodes.enableNodeSet(msg.id);
|
||||||
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
||||||
|
} else {
|
||||||
|
$.get('nodes/'+msg.id, function(data) {
|
||||||
|
$("body").append(data);
|
||||||
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (topic == "notification/node/disabled") {
|
||||||
|
if (msg.types) {
|
||||||
|
RED.nodes.disableNodeSet(msg.id);
|
||||||
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||||
|
}
|
||||||
|
} else if (topic == "node/upgraded") {
|
||||||
|
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
|
||||||
|
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
|
||||||
|
}
|
||||||
|
// Refresh flow library to ensure any examples are updated
|
||||||
|
RED.library.loadFlowLibrary();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showAbout() {
|
function showAbout() {
|
||||||
$.get('red/about', function(data) {
|
$.get('red/about', function(data) {
|
||||||
var aboutHeader = '<div style="text-align:center;">'+
|
var aboutHeader = '<div style="text-align:center;">'+
|
||||||
@ -196,6 +318,14 @@
|
|||||||
|
|
||||||
function loadEditor() {
|
function loadEditor() {
|
||||||
var menuOptions = [];
|
var menuOptions = [];
|
||||||
|
if (RED.settings.theme("projects.enabled",false)) {
|
||||||
|
menuOptions.push({id:"menu-item-projects-menu",label:"Projects",options:[
|
||||||
|
{id:"menu-item-projects-new",label:"New...",disabled:false,onselect:"core:new-project"},
|
||||||
|
{id:"menu-item-projects-open",label:"Open...",disabled:false,onselect:"core:open-project"}
|
||||||
|
]});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||||
// {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
|
// {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
|
||||||
// {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
|
// {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
|
||||||
@ -258,9 +388,18 @@
|
|||||||
RED.palette.init();
|
RED.palette.init();
|
||||||
if (RED.settings.theme('palette.editable') !== false) {
|
if (RED.settings.theme('palette.editable') !== false) {
|
||||||
RED.palette.editor.init();
|
RED.palette.editor.init();
|
||||||
|
} else {
|
||||||
|
console.log("Palette editor disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
RED.sidebar.init();
|
RED.sidebar.init();
|
||||||
|
|
||||||
|
if (RED.settings.theme("projects.enabled",false)) {
|
||||||
|
RED.projects.init();
|
||||||
|
} else {
|
||||||
|
console.log("Projects disabled");
|
||||||
|
}
|
||||||
|
|
||||||
RED.subflow.init();
|
RED.subflow.init();
|
||||||
RED.workspaces.init();
|
RED.workspaces.init();
|
||||||
RED.clipboard.init();
|
RED.clipboard.init();
|
||||||
@ -271,6 +410,7 @@
|
|||||||
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
|
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
|
||||||
|
|
||||||
RED.deploy.init(RED.settings.theme("deployButton",null));
|
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||||
|
RED.notifications.init();
|
||||||
|
|
||||||
RED.actions.add("core:show-about", showAbout);
|
RED.actions.add("core:show-about", showAbout);
|
||||||
RED.nodes.init();
|
RED.nodes.init();
|
||||||
|
@ -723,7 +723,9 @@ RED.nodes = (function() {
|
|||||||
if (!$.isArray(newNodes)) {
|
if (!$.isArray(newNodes)) {
|
||||||
newNodes = [newNodes];
|
newNodes = [newNodes];
|
||||||
}
|
}
|
||||||
|
var isInitialLoad = false;
|
||||||
if (!initialLoad) {
|
if (!initialLoad) {
|
||||||
|
isInitialLoad = true;
|
||||||
initialLoad = JSON.parse(JSON.stringify(newNodes));
|
initialLoad = JSON.parse(JSON.stringify(newNodes));
|
||||||
}
|
}
|
||||||
var unknownTypes = [];
|
var unknownTypes = [];
|
||||||
@ -744,7 +746,7 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (unknownTypes.length > 0) {
|
if (!isInitialLoad && unknownTypes.length > 0) {
|
||||||
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
|
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
|
||||||
var type = "type"+(unknownTypes.length > 1?"s":"");
|
var type = "type"+(unknownTypes.length > 1?"s":"");
|
||||||
RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000);
|
RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000);
|
||||||
@ -1223,12 +1225,13 @@ RED.nodes = (function() {
|
|||||||
RED.workspaces.remove(workspaces[id]);
|
RED.workspaces.remove(workspaces[id]);
|
||||||
});
|
});
|
||||||
defaultWorkspace = null;
|
defaultWorkspace = null;
|
||||||
|
initialLoad = null;
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(false);
|
||||||
RED.view.redraw(true);
|
RED.view.redraw(true);
|
||||||
RED.palette.refresh();
|
RED.palette.refresh();
|
||||||
RED.workspaces.refresh();
|
RED.workspaces.refresh();
|
||||||
RED.sidebar.config.refresh();
|
RED.sidebar.config.refresh();
|
||||||
|
RED.sidebar.info.refresh();
|
||||||
|
|
||||||
// var node_defs = {};
|
// var node_defs = {};
|
||||||
// var nodes = [];
|
// var nodes = [];
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
RED.settings = (function () {
|
RED.settings = (function () {
|
||||||
|
|
||||||
var loadedSettings = {};
|
var loadedSettings = {};
|
||||||
|
var userSettings = {};
|
||||||
|
var settingsDirty = false;
|
||||||
|
var pendingSave;
|
||||||
|
|
||||||
var hasLocalStorage = function () {
|
var hasLocalStorage = function () {
|
||||||
try {
|
try {
|
||||||
@ -31,7 +34,12 @@ RED.settings = (function () {
|
|||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
if (key === "auth-tokens") {
|
||||||
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
userSettings[key] = value;
|
||||||
|
saveUserSettings();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,14 +52,23 @@ RED.settings = (function () {
|
|||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return JSON.parse(localStorage.getItem(key));
|
if (key === "auth-tokens") {
|
||||||
|
return JSON.parse(localStorage.getItem(key));
|
||||||
|
} else {
|
||||||
|
return userSettings[key];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var remove = function (key) {
|
var remove = function (key) {
|
||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localStorage.removeItem(key);
|
if (key === "auth-tokens") {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
} else {
|
||||||
|
delete userSettings[key];
|
||||||
|
saveUserSettings();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var setProperties = function(data) {
|
var setProperties = function(data) {
|
||||||
@ -68,6 +85,10 @@ RED.settings = (function () {
|
|||||||
loadedSettings = data;
|
loadedSettings = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var setUserSettings = function(data) {
|
||||||
|
userSettings = data;
|
||||||
|
}
|
||||||
|
|
||||||
var init = function (done) {
|
var init = function (done) {
|
||||||
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
||||||
if (accessTokenMatch) {
|
if (accessTokenMatch) {
|
||||||
@ -106,7 +127,7 @@ RED.settings = (function () {
|
|||||||
RED.settings.remove("auth-tokens");
|
RED.settings.remove("auth-tokens");
|
||||||
}
|
}
|
||||||
console.log("Node-RED: " + data.version);
|
console.log("Node-RED: " + data.version);
|
||||||
done();
|
loadUserSettings(done);
|
||||||
},
|
},
|
||||||
error: function(jqXHR,textStatus,errorThrown) {
|
error: function(jqXHR,textStatus,errorThrown) {
|
||||||
if (jqXHR.status === 401) {
|
if (jqXHR.status === 401) {
|
||||||
@ -115,12 +136,52 @@ RED.settings = (function () {
|
|||||||
}
|
}
|
||||||
RED.user.login(function() { load(done); });
|
RED.user.login(function() { load(done); });
|
||||||
} else {
|
} else {
|
||||||
console.log("Unexpected error:",jqXHR.status,textStatus);
|
console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function loadUserSettings(done) {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
},
|
||||||
|
dataType: "json",
|
||||||
|
cache: false,
|
||||||
|
url: 'settings/user',
|
||||||
|
success: function (data) {
|
||||||
|
setUserSettings(data);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: function(jqXHR,textStatus,errorThrown) {
|
||||||
|
console.log("Unexpected error loading user settings:",jqXHR.status,textStatus);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveUserSettings() {
|
||||||
|
if (RED.user.hasPermission("settings.write")) {
|
||||||
|
if (pendingSave) {
|
||||||
|
clearTimeout(pendingSave);
|
||||||
|
}
|
||||||
|
pendingSave = setTimeout(function() {
|
||||||
|
pendingSave = null;
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
url: 'settings/user',
|
||||||
|
data: JSON.stringify(userSettings),
|
||||||
|
success: function (data) {
|
||||||
|
},
|
||||||
|
error: function(jqXHR,textStatus,errorThrown) {
|
||||||
|
console.log("Unexpected error saving user settings:",jqXHR.status,textStatus);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function theme(property,defaultValue) {
|
function theme(property,defaultValue) {
|
||||||
if (!RED.settings.editorTheme) {
|
if (!RED.settings.editorTheme) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@ -143,10 +204,10 @@ RED.settings = (function () {
|
|||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
load: load,
|
load: load,
|
||||||
|
loadUserSettings: loadUserSettings,
|
||||||
set: set,
|
set: set,
|
||||||
get: get,
|
get: get,
|
||||||
remove: remove,
|
remove: remove,
|
||||||
theme: theme
|
theme: theme
|
||||||
}
|
}
|
||||||
})
|
})();
|
||||||
();
|
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
addLabel = 'add';
|
addLabel = 'add';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$('<a href="#" class="editor-button editor-button-small" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
|
$('<a href="#" class="editor-button editor-button-small red-ui-editableList-addButton" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
|
||||||
.appendTo(this.topContainer)
|
.appendTo(this.topContainer)
|
||||||
.click(function(evt) {
|
.click(function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@ -116,6 +116,11 @@
|
|||||||
this.uiContainer.css("minHeight",minHeight);
|
this.uiContainer.css("minHeight",minHeight);
|
||||||
this.element.css("minHeight",0);
|
this.element.css("minHeight",0);
|
||||||
}
|
}
|
||||||
|
var maxHeight = this.element.css("maxHeight");
|
||||||
|
if (maxHeight !== '0px') {
|
||||||
|
this.uiContainer.css("maxHeight",maxHeight);
|
||||||
|
this.element.css("maxHeight",null);
|
||||||
|
}
|
||||||
if (this.options.height !== 'auto') {
|
if (this.options.height !== 'auto') {
|
||||||
this.uiContainer.css("overflow-y","scroll");
|
this.uiContainer.css("overflow-y","scroll");
|
||||||
if (!isNaN(this.options.height)) {
|
if (!isNaN(this.options.height)) {
|
||||||
|
@ -23,7 +23,7 @@ RED.popover = (function() {
|
|||||||
},
|
},
|
||||||
"small": {
|
"small": {
|
||||||
top: 5,
|
top: 5,
|
||||||
leftRight: 8,
|
leftRight: 17,
|
||||||
leftLeft: 16
|
leftLeft: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,6 +33,7 @@ RED.popover = (function() {
|
|||||||
var trigger = options.trigger;
|
var trigger = options.trigger;
|
||||||
var content = options.content;
|
var content = options.content;
|
||||||
var delay = options.delay;
|
var delay = options.delay;
|
||||||
|
var autoClose = options.autoClose;
|
||||||
var width = options.width||"auto";
|
var width = options.width||"auto";
|
||||||
var size = options.size||"default";
|
var size = options.size||"default";
|
||||||
if (!deltaSizes[size]) {
|
if (!deltaSizes[size]) {
|
||||||
@ -43,7 +44,7 @@ RED.popover = (function() {
|
|||||||
var active;
|
var active;
|
||||||
var div;
|
var div;
|
||||||
|
|
||||||
var openPopup = function() {
|
var openPopup = function(instant) {
|
||||||
if (active) {
|
if (active) {
|
||||||
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>').appendTo("body");
|
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>').appendTo("body");
|
||||||
if (size !== "default") {
|
if (size !== "default") {
|
||||||
@ -62,7 +63,6 @@ RED.popover = (function() {
|
|||||||
var targetPos = target.offset();
|
var targetPos = target.offset();
|
||||||
var targetWidth = target.width();
|
var targetWidth = target.width();
|
||||||
var targetHeight = target.height();
|
var targetHeight = target.height();
|
||||||
|
|
||||||
var divHeight = div.height();
|
var divHeight = div.height();
|
||||||
var divWidth = div.width();
|
var divWidth = div.width();
|
||||||
if (direction === 'right') {
|
if (direction === 'right') {
|
||||||
@ -70,23 +70,29 @@ RED.popover = (function() {
|
|||||||
} else if (direction === 'left') {
|
} else if (direction === 'left') {
|
||||||
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth});
|
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth});
|
||||||
}
|
}
|
||||||
|
if (instant) {
|
||||||
div.fadeIn("fast");
|
div.show();
|
||||||
|
} else {
|
||||||
|
div.fadeIn("fast");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var closePopup = function() {
|
var closePopup = function(instant) {
|
||||||
if (!active) {
|
if (!active) {
|
||||||
if (div) {
|
if (div) {
|
||||||
div.fadeOut("fast",function() {
|
if (instant) {
|
||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
} else {
|
||||||
|
div.fadeOut("fast",function() {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
div = null;
|
div = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trigger === 'hover') {
|
if (trigger === 'hover') {
|
||||||
|
|
||||||
target.on('mouseenter',function(e) {
|
target.on('mouseenter',function(e) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
active = true;
|
active = true;
|
||||||
@ -110,18 +116,26 @@ RED.popover = (function() {
|
|||||||
openPopup();
|
openPopup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (autoClose) {
|
||||||
|
setTimeout(function() {
|
||||||
|
active = false;
|
||||||
|
closePopup();
|
||||||
|
},autoClose);
|
||||||
}
|
}
|
||||||
var res = {
|
var res = {
|
||||||
setContent: function(_content) {
|
setContent: function(_content) {
|
||||||
content = _content;
|
content = _content;
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
open: function () {
|
open: function (instant) {
|
||||||
active = true;
|
active = true;
|
||||||
openPopup();
|
openPopup(instant);
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function (instant) {
|
||||||
active = false;
|
active = false;
|
||||||
closePopup();
|
closePopup(instant);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -17,11 +17,31 @@
|
|||||||
RED.stack = (function() {
|
RED.stack = (function() {
|
||||||
function createStack(options) {
|
function createStack(options) {
|
||||||
var container = options.container;
|
var container = options.container;
|
||||||
|
container.addClass("red-ui-stack");
|
||||||
|
var contentHeight = 0;
|
||||||
var entries = [];
|
var entries = [];
|
||||||
|
|
||||||
var visible = true;
|
var visible = true;
|
||||||
|
// TODO: make this a singleton function - and watch out for stacks no longer
|
||||||
|
// in the DOM
|
||||||
|
var resizeStack = function() {
|
||||||
|
if (entries.length > 0) {
|
||||||
|
var headerHeight = 0;
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
headerHeight += entry.header.outerHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
var height = container.innerHeight();
|
||||||
|
contentHeight = height - headerHeight - (entries.length-1);
|
||||||
|
entries.forEach(function(e) {
|
||||||
|
e.contentWrap.height(contentHeight);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.fill && options.singleExpanded) {
|
||||||
|
$(window).resize(resizeStack);
|
||||||
|
$(window).focus(resizeStack);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
add: function(entry) {
|
add: function(entry) {
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
@ -30,7 +50,12 @@ RED.stack = (function() {
|
|||||||
entry.container.hide();
|
entry.container.hide();
|
||||||
}
|
}
|
||||||
var header = $('<div class="palette-header"></div>').appendTo(entry.container);
|
var header = $('<div class="palette-header"></div>').appendTo(entry.container);
|
||||||
entry.content = $('<div></div>').appendTo(entry.container);
|
entry.header = header;
|
||||||
|
entry.contentWrap = $('<div></div>',{style:"position:relative"}).appendTo(entry.container);
|
||||||
|
if (options.fill) {
|
||||||
|
entry.contentWrap.css("height",contentHeight);
|
||||||
|
}
|
||||||
|
entry.content = $('<div></div>').appendTo(entry.contentWrap);
|
||||||
if (entry.collapsible !== false) {
|
if (entry.collapsible !== false) {
|
||||||
header.click(function() {
|
header.click(function() {
|
||||||
if (options.singleExpanded) {
|
if (options.singleExpanded) {
|
||||||
@ -49,11 +74,13 @@ RED.stack = (function() {
|
|||||||
var icon = $('<i class="fa fa-angle-down"></i>').appendTo(header);
|
var icon = $('<i class="fa fa-angle-down"></i>').appendTo(header);
|
||||||
|
|
||||||
if (entry.expanded) {
|
if (entry.expanded) {
|
||||||
|
entry.container.addClass("palette-category-expanded");
|
||||||
icon.addClass("expanded");
|
icon.addClass("expanded");
|
||||||
} else {
|
} else {
|
||||||
entry.content.hide();
|
entry.contentWrap.hide();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
$('<i style="opacity: 0.5;" class="fa fa-angle-down expanded"></i>').appendTo(header);
|
||||||
header.css("cursor","default");
|
header.css("cursor","default");
|
||||||
}
|
}
|
||||||
entry.title = $('<span></span>').html(entry.title).appendTo(header);
|
entry.title = $('<span></span>').html(entry.title).appendTo(header);
|
||||||
@ -74,24 +101,35 @@ RED.stack = (function() {
|
|||||||
if (entry.onexpand) {
|
if (entry.onexpand) {
|
||||||
entry.onexpand.call(entry);
|
entry.onexpand.call(entry);
|
||||||
}
|
}
|
||||||
|
if (options.singleExpanded) {
|
||||||
|
entries.forEach(function(e) {
|
||||||
|
if (e !== entry) {
|
||||||
|
e.collapse();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
icon.addClass("expanded");
|
icon.addClass("expanded");
|
||||||
entry.content.slideDown(200);
|
entry.container.addClass("palette-category-expanded");
|
||||||
|
entry.contentWrap.slideDown(200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
entry.collapse = function() {
|
entry.collapse = function() {
|
||||||
if (entry.isExpanded()) {
|
if (entry.isExpanded()) {
|
||||||
icon.removeClass("expanded");
|
icon.removeClass("expanded");
|
||||||
entry.content.slideUp(200);
|
entry.container.removeClass("palette-category-expanded");
|
||||||
|
entry.contentWrap.slideUp(200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
entry.isExpanded = function() {
|
entry.isExpanded = function() {
|
||||||
return icon.hasClass("expanded");
|
return entry.container.hasClass("palette-category-expanded");
|
||||||
};
|
};
|
||||||
|
if (options.fill && options.singleExpanded) {
|
||||||
|
resizeStack();
|
||||||
|
}
|
||||||
return entry;
|
return entry;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hide: function() {
|
hide: function() {
|
||||||
@ -108,9 +146,13 @@ RED.stack = (function() {
|
|||||||
entry.container.show();
|
entry.container.show();
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
|
},
|
||||||
|
resize: function() {
|
||||||
|
if (resizeStack) {
|
||||||
|
resizeStack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -97,113 +97,6 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
RED.actions.add("core:deploy-flows",save);
|
RED.actions.add("core:deploy-flows",save);
|
||||||
|
|
||||||
$( "#node-dialog-confirm-deploy" ).dialog({
|
|
||||||
title: RED._('deploy.confirm.button.confirm'),
|
|
||||||
modal: true,
|
|
||||||
autoOpen: false,
|
|
||||||
width: 550,
|
|
||||||
height: "auto",
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: RED._("common.label.cancel"),
|
|
||||||
click: function() {
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "node-dialog-confirm-deploy-review",
|
|
||||||
text: RED._("deploy.confirm.button.review"),
|
|
||||||
class: "primary disabled",
|
|
||||||
click: function() {
|
|
||||||
if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) {
|
|
||||||
RED.diff.showRemoteDiff();
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "node-dialog-confirm-deploy-merge",
|
|
||||||
text: RED._("deploy.confirm.button.merge"),
|
|
||||||
class: "primary disabled",
|
|
||||||
click: function() {
|
|
||||||
RED.diff.mergeDiff(currentDiff);
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "node-dialog-confirm-deploy-deploy",
|
|
||||||
text: RED._("deploy.confirm.button.confirm"),
|
|
||||||
class: "primary",
|
|
||||||
click: function() {
|
|
||||||
|
|
||||||
var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked");
|
|
||||||
if (ignoreChecked) {
|
|
||||||
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
|
|
||||||
}
|
|
||||||
save(true,/conflict/.test($("#node-dialog-confirm-deploy-type" ).val()));
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "node-dialog-confirm-deploy-overwrite",
|
|
||||||
text: RED._("deploy.confirm.button.overwrite"),
|
|
||||||
class: "primary",
|
|
||||||
click: function() {
|
|
||||||
save(true,/conflict/.test($("#node-dialog-confirm-deploy-type" ).val()));
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
create: function() {
|
|
||||||
$("#node-dialog-confirm-deploy").parent().find("div.ui-dialog-buttonpane")
|
|
||||||
.prepend('<div style="height:0; vertical-align: middle; display:inline-block; margin-top: 13px; float:left;">'+
|
|
||||||
'<input style="vertical-align:top;" type="checkbox" id="node-dialog-confirm-deploy-hide"> '+
|
|
||||||
'<label style="display:inline;" for="node-dialog-confirm-deploy-hide" data-i18n="deploy.confirm.doNotWarn"></label>'+
|
|
||||||
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
|
|
||||||
'</div>');
|
|
||||||
},
|
|
||||||
open: function() {
|
|
||||||
var deployType = $("#node-dialog-confirm-deploy-type" ).val();
|
|
||||||
if (/conflict/.test(deployType)) {
|
|
||||||
$( "#node-dialog-confirm-deploy" ).dialog('option','title', RED._('deploy.confirm.button.review'));
|
|
||||||
$("#node-dialog-confirm-deploy-deploy").hide();
|
|
||||||
$("#node-dialog-confirm-deploy-review").addClass('disabled').show();
|
|
||||||
$("#node-dialog-confirm-deploy-merge").addClass('disabled').show();
|
|
||||||
$("#node-dialog-confirm-deploy-overwrite").toggle(deployType === "deploy-conflict");
|
|
||||||
currentDiff = null;
|
|
||||||
$("#node-dialog-confirm-deploy-conflict-checking").show();
|
|
||||||
$("#node-dialog-confirm-deploy-conflict-auto-merge").hide();
|
|
||||||
$("#node-dialog-confirm-deploy-conflict-manual-merge").hide();
|
|
||||||
|
|
||||||
var now = Date.now();
|
|
||||||
RED.diff.getRemoteDiff(function(diff) {
|
|
||||||
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
|
|
||||||
currentDiff = diff;
|
|
||||||
setTimeout(function() {
|
|
||||||
$("#node-dialog-confirm-deploy-conflict-checking").hide();
|
|
||||||
var d = Object.keys(diff.conflicts);
|
|
||||||
if (d.length === 0) {
|
|
||||||
$("#node-dialog-confirm-deploy-conflict-auto-merge").show();
|
|
||||||
$("#node-dialog-confirm-deploy-merge").removeClass('disabled')
|
|
||||||
} else {
|
|
||||||
$("#node-dialog-confirm-deploy-conflict-manual-merge").show();
|
|
||||||
}
|
|
||||||
$("#node-dialog-confirm-deploy-review").removeClass('disabled')
|
|
||||||
},ellapsed);
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
$("#node-dialog-confirm-deploy-hide").parent().hide();
|
|
||||||
} else {
|
|
||||||
$( "#node-dialog-confirm-deploy" ).dialog('option','title', RED._('deploy.confirm.button.confirm'));
|
|
||||||
$("#node-dialog-confirm-deploy-deploy").show();
|
|
||||||
$("#node-dialog-confirm-deploy-overwrite").hide();
|
|
||||||
$("#node-dialog-confirm-deploy-review").hide();
|
|
||||||
$("#node-dialog-confirm-deploy-merge").hide();
|
|
||||||
$("#node-dialog-confirm-deploy-hide").parent().show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
RED.events.on('nodes:change',function(state) {
|
RED.events.on('nodes:change',function(state) {
|
||||||
if (state.dirty) {
|
if (state.dirty) {
|
||||||
@ -224,24 +117,30 @@ RED.deploy = (function() {
|
|||||||
if (currentRev === null || deployInflight || currentRev === msg.revision) {
|
if (currentRev === null || deployInflight || currentRev === msg.revision) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var message = $('<div>'+RED._('deploy.confirm.backgroundUpdate')+
|
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
|
||||||
'<br><br><div class="ui-dialog-buttonset">'+
|
activeNotifyMessage = RED.notify(message,{
|
||||||
'<button>'+RED._('deploy.confirm.button.ignore')+'</button>'+
|
modal: true,
|
||||||
'<button class="primary">'+RED._('deploy.confirm.button.review')+'</button>'+
|
fixed: true,
|
||||||
'</div></div>');
|
buttons: [
|
||||||
$(message.find('button')[0]).click(function(evt) {
|
{
|
||||||
evt.preventDefault();
|
text: RED._('deploy.confirm.button.ignore'),
|
||||||
activeNotifyMessage.close();
|
click: function() {
|
||||||
activeNotifyMessage = null;
|
activeNotifyMessage.close();
|
||||||
})
|
activeNotifyMessage = null;
|
||||||
$(message.find('button')[1]).click(function(evt) {
|
}
|
||||||
evt.preventDefault();
|
},
|
||||||
activeNotifyMessage.close();
|
{
|
||||||
var nns = RED.nodes.createCompleteNodeSet();
|
text: RED._('deploy.confirm.button.review'),
|
||||||
resolveConflict(nns,false);
|
class: "primary",
|
||||||
activeNotifyMessage = null;
|
click: function() {
|
||||||
})
|
activeNotifyMessage.close();
|
||||||
activeNotifyMessage = RED.notify(message,null,true);
|
var nns = RED.nodes.createCompleteNodeSet();
|
||||||
|
resolveConflict(nns,false);
|
||||||
|
activeNotifyMessage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -271,16 +170,99 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveConflict(currentNodes, activeDeploy) {
|
function resolveConflict(currentNodes, activeDeploy) {
|
||||||
$( "#node-dialog-confirm-deploy-config" ).hide();
|
var message = $('<div>');
|
||||||
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
$('<p data-i18n="deploy.confirm.conflict"></p>').appendTo(message);
|
||||||
$( "#node-dialog-confirm-deploy-unused" ).hide();
|
var conflictCheck = $('<div id="node-dialog-confirm-deploy-conflict-checking" class="node-dialog-confirm-conflict-row">'+
|
||||||
$( "#node-dialog-confirm-deploy-conflict" ).show();
|
'<img src="red/images/spin.svg"/><div data-i18n="deploy.confirm.conflictChecking"></div>'+
|
||||||
$( "#node-dialog-confirm-deploy-type" ).val(activeDeploy?"deploy-conflict":"background-conflict");
|
'</div>').appendTo(message);
|
||||||
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
|
var conflictAutoMerge = $('<div class="node-dialog-confirm-conflict-row">'+
|
||||||
}
|
'<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>'+
|
||||||
|
'</div>').hide().appendTo(message);
|
||||||
|
var conflictManualMerge = $('<div id="node-dialog-confirm-deploy-conflict-manual-merge" class="node-dialog-confirm-conflict-row">'+
|
||||||
|
'<i style="color: #999;" class="fa fa-exclamation"></i><div data-i18n="deploy.confirm.conflictManualMerge"></div>'+
|
||||||
|
'</div>').hide().appendTo(message);
|
||||||
|
|
||||||
|
message.i18n();
|
||||||
|
currentDiff = null;
|
||||||
|
var buttons = [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
conflictNotification.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "node-dialog-confirm-deploy-review",
|
||||||
|
text: RED._("deploy.confirm.button.review"),
|
||||||
|
class: "primary disabled",
|
||||||
|
click: function() {
|
||||||
|
if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) {
|
||||||
|
RED.diff.showRemoteDiff();
|
||||||
|
conflictNotification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "node-dialog-confirm-deploy-merge",
|
||||||
|
text: RED._("deploy.confirm.button.merge"),
|
||||||
|
class: "primary disabled",
|
||||||
|
click: function() {
|
||||||
|
if (!$("#node-dialog-confirm-deploy-merge").hasClass('disabled')) {
|
||||||
|
RED.diff.mergeDiff(currentDiff);
|
||||||
|
conflictNotification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (activeDeploy) {
|
||||||
|
buttons.push({
|
||||||
|
id: "node-dialog-confirm-deploy-overwrite",
|
||||||
|
text: RED._("deploy.confirm.button.overwrite"),
|
||||||
|
class: "primary",
|
||||||
|
click: function() {
|
||||||
|
save(true,activeDeploy);
|
||||||
|
conflictNotification.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var conflictNotification = RED.notify(message,{
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
width: 600,
|
||||||
|
buttons: buttons
|
||||||
|
});
|
||||||
|
|
||||||
|
var now = Date.now();
|
||||||
|
RED.diff.getRemoteDiff(function(diff) {
|
||||||
|
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
|
||||||
|
currentDiff = diff;
|
||||||
|
setTimeout(function() {
|
||||||
|
conflictCheck.hide();
|
||||||
|
var d = Object.keys(diff.conflicts);
|
||||||
|
if (d.length === 0) {
|
||||||
|
conflictAutoMerge.show();
|
||||||
|
$("#node-dialog-confirm-deploy-merge").removeClass('disabled')
|
||||||
|
} else {
|
||||||
|
conflictManualMerge.show();
|
||||||
|
}
|
||||||
|
$("#node-dialog-confirm-deploy-review").removeClass('disabled')
|
||||||
|
},ellapsed);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function cropList(list) {
|
||||||
|
if (list.length > 5) {
|
||||||
|
var remainder = list.length - 5;
|
||||||
|
list = list.slice(0,5);
|
||||||
|
list.push(RED._("deploy.confirm.plusNMore",{count:remainder}));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
function save(skipValidation,force) {
|
function save(skipValidation,force) {
|
||||||
if (!$("#btn-deploy").hasClass("disabled")) {
|
if (!$("#btn-deploy").hasClass("disabled")) {
|
||||||
|
if (!RED.user.hasPermission("flows.write")) {
|
||||||
|
RED.notify(RED._("user.errors.deploy"),"error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!skipValidation) {
|
if (!skipValidation) {
|
||||||
var hasUnknown = false;
|
var hasUnknown = false;
|
||||||
var hasInvalid = false;
|
var hasInvalid = false;
|
||||||
@ -310,39 +292,62 @@ 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;
|
var showWarning = false;
|
||||||
|
var notificationMessage;
|
||||||
|
var notificationButtons = [];
|
||||||
|
var notification;
|
||||||
if (hasUnknown && !ignoreDeployWarnings.unknown) {
|
if (hasUnknown && !ignoreDeployWarnings.unknown) {
|
||||||
showWarning = true;
|
showWarning = true;
|
||||||
$( "#node-dialog-confirm-deploy-type" ).val("unknown");
|
notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
|
||||||
$( "#node-dialog-confirm-deploy-unknown" ).show();
|
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(unknownNodes).join("</li><li>")+"</li></ul><p>"+
|
||||||
$( "#node-dialog-confirm-deploy-unknown-list" )
|
RED._('deploy.confirm.confirm')+
|
||||||
.html("<li>"+unknownNodes.join("</li><li>")+"</li>");
|
"</p>";
|
||||||
|
|
||||||
|
notificationButtons= [
|
||||||
|
{
|
||||||
|
id: "node-dialog-confirm-deploy-deploy",
|
||||||
|
text: RED._("deploy.confirm.button.confirm"),
|
||||||
|
class: "primary",
|
||||||
|
click: function() {
|
||||||
|
save(true);
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
|
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
|
||||||
showWarning = true;
|
showWarning = true;
|
||||||
$( "#node-dialog-confirm-deploy-type" ).val("invalid");
|
|
||||||
$( "#node-dialog-confirm-deploy-config" ).show();
|
|
||||||
invalidNodes.sort(sortNodeInfo);
|
invalidNodes.sort(sortNodeInfo);
|
||||||
$( "#node-dialog-confirm-deploy-invalid-list" )
|
|
||||||
.html("<li>"+invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
|
|
||||||
|
|
||||||
} else if (hasUnusedConfig && !ignoreDeployWarnings.unusedConfig) {
|
notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
|
||||||
// showWarning = true;
|
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"})).join("</li><li>")+"</li></ul><p>"+
|
||||||
// $( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
|
RED._('deploy.confirm.confirm')+
|
||||||
// $( "#node-dialog-confirm-deploy-unused" ).show();
|
"</p>";
|
||||||
//
|
notificationButtons= [
|
||||||
// unusedConfigNodes.sort(sortNodeInfo);
|
{
|
||||||
// $( "#node-dialog-confirm-deploy-unused-list" )
|
id: "node-dialog-confirm-deploy-deploy",
|
||||||
// .html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
|
text: RED._("deploy.confirm.button.confirm"),
|
||||||
|
class: "primary",
|
||||||
|
click: function() {
|
||||||
|
save(true);
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
if (showWarning) {
|
if (showWarning) {
|
||||||
$( "#node-dialog-confirm-deploy-hide" ).prop("checked",false);
|
notificationButtons.unshift(
|
||||||
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
notification = RED.notify(notificationMessage,{
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons:notificationButtons
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -382,7 +387,7 @@ RED.deploy = (function() {
|
|||||||
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
||||||
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
|
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
|
||||||
} else {
|
} else {
|
||||||
RED.notify(RED._("deploy.successfulDeploy"),"success");
|
RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success");
|
||||||
}
|
}
|
||||||
RED.nodes.eachNode(function(node) {
|
RED.nodes.eachNode(function(node) {
|
||||||
if (node.changed) {
|
if (node.changed) {
|
||||||
@ -437,6 +442,10 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
init: init
|
init: init,
|
||||||
|
setDeployInflight: function(state) {
|
||||||
|
deployInflight = state;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
1187
editor/js/ui/diff.js
1187
editor/js/ui/diff.js
File diff suppressed because it is too large
Load Diff
@ -517,13 +517,16 @@ RED.editor = (function() {
|
|||||||
|
|
||||||
function getEditStackTitle() {
|
function getEditStackTitle() {
|
||||||
var title = '<ul class="editor-tray-breadcrumbs">';
|
var title = '<ul class="editor-tray-breadcrumbs">';
|
||||||
for (var i=0;i<editStack.length;i++) {
|
var label;
|
||||||
|
for (var i=editStack.length-1;i<editStack.length;i++) {
|
||||||
var node = editStack[i];
|
var node = editStack[i];
|
||||||
var label = node.type;
|
label = node.type;
|
||||||
if (node.type === '_expression') {
|
if (node.type === '_expression') {
|
||||||
label = RED._("expressionEditor.title");
|
label = RED._("expressionEditor.title");
|
||||||
} else if (node.type === '_json') {
|
} else if (node.type === '_json') {
|
||||||
label = RED._("jsonEditor.title");
|
label = RED._("jsonEditor.title");
|
||||||
|
} else if (node.type === '_markdown') {
|
||||||
|
label = RED._("markdownEditor.title");
|
||||||
} else if (node.type === '_buffer') {
|
} else if (node.type === '_buffer') {
|
||||||
label = RED._("bufferEditor.title");
|
label = RED._("bufferEditor.title");
|
||||||
} else if (node.type === 'subflow') {
|
} else if (node.type === 'subflow') {
|
||||||
@ -550,7 +553,7 @@ RED.editor = (function() {
|
|||||||
title += '<li>'+label+'</li>';
|
title += '<li>'+label+'</li>';
|
||||||
}
|
}
|
||||||
title += '</ul>';
|
title += '</ul>';
|
||||||
return title;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildEditForm(container,formId,type,ns) {
|
function buildEditForm(container,formId,type,ns) {
|
||||||
@ -2119,9 +2122,105 @@ RED.editor = (function() {
|
|||||||
editStack.push({type:type});
|
editStack.push({type:type});
|
||||||
RED.view.state(RED.state.EDITING);
|
RED.view.state(RED.state.EDITING);
|
||||||
var expressionEditor;
|
var expressionEditor;
|
||||||
|
var changeTimer;
|
||||||
|
|
||||||
|
var checkValid = function() {
|
||||||
|
var v = expressionEditor.getValue();
|
||||||
|
try {
|
||||||
|
JSON.parse(v);
|
||||||
|
$("#node-dialog-ok").removeClass('disabled');
|
||||||
|
return true;
|
||||||
|
} catch(err) {
|
||||||
|
$("#node-dialog-ok").addClass('disabled');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var trayOptions = {
|
||||||
|
title: options.title || getEditStackTitle(),
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
id: "node-dialog-cancel",
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
RED.tray.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "node-dialog-ok",
|
||||||
|
text: RED._("common.label.done"),
|
||||||
|
class: "primary",
|
||||||
|
click: function() {
|
||||||
|
if (options.requireValid && !checkValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onComplete(expressionEditor.getValue());
|
||||||
|
RED.tray.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
resize: function(dimensions) {
|
||||||
|
editTrayWidthCache[type] = dimensions.width;
|
||||||
|
|
||||||
|
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||||
|
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||||
|
var height = $("#dialog-form").height();
|
||||||
|
for (var i=0;i<rows.size();i++) {
|
||||||
|
height -= $(rows[i]).outerHeight(true);
|
||||||
|
}
|
||||||
|
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
|
||||||
|
$(".node-text-editor").css("height",height+"px");
|
||||||
|
expressionEditor.resize();
|
||||||
|
},
|
||||||
|
open: function(tray) {
|
||||||
|
var trayBody = tray.find('.editor-tray-body');
|
||||||
|
var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
|
||||||
|
expressionEditor = RED.editor.createEditor({
|
||||||
|
id: 'node-input-json',
|
||||||
|
value: "",
|
||||||
|
mode:"ace/mode/json"
|
||||||
|
});
|
||||||
|
expressionEditor.getSession().setValue(value||"",-1);
|
||||||
|
if (options.requireValid) {
|
||||||
|
expressionEditor.getSession().on('change', function() {
|
||||||
|
clearTimeout(changeTimer);
|
||||||
|
changeTimer = setTimeout(checkValid,200);
|
||||||
|
});
|
||||||
|
checkValid();
|
||||||
|
}
|
||||||
|
$("#node-input-json-reformat").click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var v = expressionEditor.getValue()||"";
|
||||||
|
try {
|
||||||
|
v = JSON.stringify(JSON.parse(v),null,4);
|
||||||
|
} catch(err) {
|
||||||
|
// TODO: do an optimistic auto-format
|
||||||
|
}
|
||||||
|
expressionEditor.getSession().setValue(v||"",-1);
|
||||||
|
});
|
||||||
|
dialogForm.i18n();
|
||||||
|
},
|
||||||
|
close: function() {
|
||||||
|
editStack.pop();
|
||||||
|
expressionEditor.destroy();
|
||||||
|
},
|
||||||
|
show: function() {}
|
||||||
|
}
|
||||||
|
if (editTrayWidthCache.hasOwnProperty(type)) {
|
||||||
|
trayOptions.width = editTrayWidthCache[type];
|
||||||
|
}
|
||||||
|
RED.tray.show(trayOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function editMarkdown(options) {
|
||||||
|
var value = options.value;
|
||||||
|
var onComplete = options.complete;
|
||||||
|
var type = "_markdown"
|
||||||
|
editStack.push({type:type});
|
||||||
|
RED.view.state(RED.state.EDITING);
|
||||||
|
var expressionEditor;
|
||||||
|
|
||||||
var trayOptions = {
|
var trayOptions = {
|
||||||
title: getEditStackTitle(),
|
title: options.title || getEditStackTitle(),
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
id: "node-dialog-cancel",
|
id: "node-dialog-cancel",
|
||||||
@ -2157,20 +2256,9 @@ RED.editor = (function() {
|
|||||||
var trayBody = tray.find('.editor-tray-body');
|
var trayBody = tray.find('.editor-tray-body');
|
||||||
var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
|
var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
|
||||||
expressionEditor = RED.editor.createEditor({
|
expressionEditor = RED.editor.createEditor({
|
||||||
id: 'node-input-json',
|
id: 'node-input-markdown',
|
||||||
value: "",
|
value: value,
|
||||||
mode:"ace/mode/json"
|
mode:"ace/mode/markdown"
|
||||||
});
|
|
||||||
expressionEditor.getSession().setValue(value||"",-1);
|
|
||||||
$("#node-input-json-reformat").click(function(evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
var v = expressionEditor.getValue()||"";
|
|
||||||
try {
|
|
||||||
v = JSON.stringify(JSON.parse(v),null,4);
|
|
||||||
} catch(err) {
|
|
||||||
// TODO: do an optimistic auto-format
|
|
||||||
}
|
|
||||||
expressionEditor.getSession().setValue(v||"",-1);
|
|
||||||
});
|
});
|
||||||
dialogForm.i18n();
|
dialogForm.i18n();
|
||||||
},
|
},
|
||||||
@ -2390,13 +2478,14 @@ RED.editor = (function() {
|
|||||||
editSubflow: showEditSubflowDialog,
|
editSubflow: showEditSubflowDialog,
|
||||||
editExpression: editExpression,
|
editExpression: editExpression,
|
||||||
editJSON: editJSON,
|
editJSON: editJSON,
|
||||||
|
editMarkdown: editMarkdown,
|
||||||
editBuffer: editBuffer,
|
editBuffer: editBuffer,
|
||||||
validateNode: validateNode,
|
validateNode: validateNode,
|
||||||
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
|
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
|
||||||
|
|
||||||
|
|
||||||
createEditor: function(options) {
|
createEditor: function(options) {
|
||||||
var editor = ace.edit(options.id);
|
var editor = ace.edit(options.id||options.element);
|
||||||
editor.setTheme("ace/theme/tomorrow");
|
editor.setTheme("ace/theme/tomorrow");
|
||||||
var session = editor.getSession();
|
var session = editor.getSession();
|
||||||
if (options.mode) {
|
if (options.mode) {
|
||||||
|
@ -57,8 +57,24 @@ RED.keyboard = (function() {
|
|||||||
173:189
|
173:189
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrateOldKeymap() {
|
||||||
|
if ('localStorage' in window && window['localStorage'] !== null) {
|
||||||
|
var oldKeyMap = localStorage.getItem("keymap");
|
||||||
|
if (oldKeyMap !== null) {
|
||||||
|
localStorage.removeItem("keymap");
|
||||||
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
currentEditorSettings.keymap = JSON.parse(oldKeyMap);
|
||||||
|
RED.settings.set('editor',currentEditorSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
function init() {
|
function init() {
|
||||||
var userKeymap = RED.settings.get('keymap') || {};
|
// Migrate from pre-0.18
|
||||||
|
migrateOldKeymap();
|
||||||
|
|
||||||
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
var userKeymap = currentEditorSettings.keymap || {};
|
||||||
|
|
||||||
$.getJSON("red/keymap.json",function(data) {
|
$.getJSON("red/keymap.json",function(data) {
|
||||||
for (var scope in data) {
|
for (var scope in data) {
|
||||||
if (data.hasOwnProperty(scope)) {
|
if (data.hasOwnProperty(scope)) {
|
||||||
@ -67,12 +83,12 @@ RED.keyboard = (function() {
|
|||||||
if (keys.hasOwnProperty(key)) {
|
if (keys.hasOwnProperty(key)) {
|
||||||
if (!userKeymap.hasOwnProperty(keys[key])) {
|
if (!userKeymap.hasOwnProperty(keys[key])) {
|
||||||
addHandler(scope,key,keys[key],false);
|
addHandler(scope,key,keys[key],false);
|
||||||
defaultKeyMap[keys[key]] = {
|
|
||||||
scope:scope,
|
|
||||||
key:key,
|
|
||||||
user:false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
defaultKeyMap[keys[key]] = {
|
||||||
|
scope:scope,
|
||||||
|
key:key,
|
||||||
|
user:false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,8 +385,11 @@ RED.keyboard = (function() {
|
|||||||
container.removeClass('keyboard-shortcut-entry-expanded');
|
container.removeClass('keyboard-shortcut-entry-expanded');
|
||||||
var shortcut = RED.keyboard.getShortcut(object.id);
|
var shortcut = RED.keyboard.getShortcut(object.id);
|
||||||
var userKeymap = RED.settings.get('keymap') || {};
|
var userKeymap = RED.settings.get('keymap') || {};
|
||||||
delete userKeymap[object.id];
|
|
||||||
RED.settings.set('keymap',userKeymap);
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
var userKeymap = currentEditorSettings.keymap || {};
|
||||||
|
userKeymap[object.id] = null;
|
||||||
|
RED.settings.set('editor',currentEditorSettings);
|
||||||
|
|
||||||
var obj = {
|
var obj = {
|
||||||
id:object.id,
|
id:object.id,
|
||||||
@ -419,9 +438,11 @@ RED.keyboard = (function() {
|
|||||||
object.scope = scope;
|
object.scope = scope;
|
||||||
RED.keyboard.add(object.scope,object.key,object.id,true);
|
RED.keyboard.add(object.scope,object.key,object.id,true);
|
||||||
}
|
}
|
||||||
var userKeymap = RED.settings.get('keymap') || {};
|
|
||||||
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
var userKeymap = currentEditorSettings.keymap || {};
|
||||||
userKeymap[object.id] = RED.keyboard.getShortcut(object.id);
|
userKeymap[object.id] = RED.keyboard.getShortcut(object.id);
|
||||||
RED.settings.set('keymap',userKeymap);
|
RED.settings.set('editor',currentEditorSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
RED.notify = (function() {
|
RED.notifications = (function() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Example usage for a modal dialog with buttons
|
// Example usage for a modal dialog with buttons
|
||||||
@ -39,9 +39,11 @@ RED.notify = (function() {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var persistentNotifications = {};
|
||||||
|
|
||||||
var currentNotifications = [];
|
var currentNotifications = [];
|
||||||
var c = 0;
|
var c = 0;
|
||||||
return function(msg,type,fixed,timeout) {
|
function notify(msg,type,fixed,timeout) {
|
||||||
var options = {};
|
var options = {};
|
||||||
if (type !== null && typeof type === 'object') {
|
if (type !== null && typeof type === 'object') {
|
||||||
options = type;
|
options = type;
|
||||||
@ -51,10 +53,7 @@ RED.notify = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.modal) {
|
if (options.modal) {
|
||||||
$("#header-shade").show();
|
$("#full-shade").show();
|
||||||
$("#editor-shade").show();
|
|
||||||
$("#palette-shade").show();
|
|
||||||
$(".sidebar-shade").show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNotifications.length > 4) {
|
if (currentNotifications.length > 4) {
|
||||||
@ -75,6 +74,16 @@ RED.notify = (function() {
|
|||||||
if (type) {
|
if (type) {
|
||||||
n.className = "notification notification-"+type;
|
n.className = "notification notification-"+type;
|
||||||
}
|
}
|
||||||
|
if (options.width) {
|
||||||
|
var parentWidth = $("#notifications").width();
|
||||||
|
if (options.width > parentWidth) {
|
||||||
|
var margin = -(options.width-parentWidth)/2;
|
||||||
|
$(n).css({
|
||||||
|
width: options.width+"px",
|
||||||
|
marginLeft: margin+"px"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
n.style.display = "none";
|
n.style.display = "none";
|
||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
n.innerHTML = msg;
|
n.innerHTML = msg;
|
||||||
@ -85,6 +94,9 @@ RED.notify = (function() {
|
|||||||
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n)
|
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n)
|
||||||
options.buttons.forEach(function(buttonDef) {
|
options.buttons.forEach(function(buttonDef) {
|
||||||
var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
|
var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
|
||||||
|
if (buttonDef.id) {
|
||||||
|
b.attr('id',buttonDef.id);
|
||||||
|
}
|
||||||
if (buttonDef.class) {
|
if (buttonDef.class) {
|
||||||
b.addClass(buttonDef.class);
|
b.addClass(buttonDef.class);
|
||||||
}
|
}
|
||||||
@ -97,33 +109,82 @@ RED.notify = (function() {
|
|||||||
n.close = (function() {
|
n.close = (function() {
|
||||||
var nn = n;
|
var nn = n;
|
||||||
return function() {
|
return function() {
|
||||||
|
if (nn.closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nn.closed = true;
|
||||||
currentNotifications.splice(currentNotifications.indexOf(nn),1);
|
currentNotifications.splice(currentNotifications.indexOf(nn),1);
|
||||||
|
if (options.id) {
|
||||||
|
delete persistentNotifications[options.id];
|
||||||
|
if (Object.keys(persistentNotifications).length === 0) {
|
||||||
|
notificationButtonWrapper.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
$(nn).slideUp(300, function() {
|
$(nn).slideUp(300, function() {
|
||||||
nn.parentNode.removeChild(nn);
|
nn.parentNode.removeChild(nn);
|
||||||
});
|
});
|
||||||
if (options.modal) {
|
if (options.modal) {
|
||||||
$("#header-shade").hide();
|
$("#full-shade").hide();
|
||||||
$("#editor-shade").hide();
|
|
||||||
$("#palette-shade").hide();
|
|
||||||
$(".sidebar-shade").hide();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
n.hideNotification = (function() {
|
||||||
|
var nn = n;
|
||||||
|
return function() {
|
||||||
|
if (nn.closed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nn.hidden = true;
|
||||||
|
$(nn).slideUp(300);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
n.showNotification = (function() {
|
||||||
|
var nn = n;
|
||||||
|
return function() {
|
||||||
|
if (nn.closed || !nn.hidden) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nn.hidden = false;
|
||||||
|
$(nn).slideDown(300);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
n.update = (function() {
|
n.update = (function() {
|
||||||
var nn = n;
|
var nn = n;
|
||||||
return function(msg,timeout) {
|
return function(msg,options) {
|
||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
nn.innerHTML = msg;
|
nn.innerHTML = msg;
|
||||||
} else {
|
} else {
|
||||||
$(nn).empty().append(msg);
|
$(nn).empty().append(msg);
|
||||||
}
|
}
|
||||||
|
var timeout;
|
||||||
|
if (typeof options === 'number') {
|
||||||
|
timeout = options;
|
||||||
|
} else if (options !== undefined) {
|
||||||
|
timeout = options.timeout;
|
||||||
|
if (options.buttons) {
|
||||||
|
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
|
||||||
|
options.buttons.forEach(function(buttonDef) {
|
||||||
|
var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
|
||||||
|
if (buttonDef.id) {
|
||||||
|
b.attr('id',buttonDef.id);
|
||||||
|
}
|
||||||
|
if (buttonDef.class) {
|
||||||
|
b.addClass(buttonDef.class);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
if (timeout !== undefined && timeout > 0) {
|
if (timeout !== undefined && timeout > 0) {
|
||||||
window.clearTimeout(nn.timeoutid);
|
window.clearTimeout(nn.timeoutid);
|
||||||
nn.timeoutid = window.setTimeout(nn.close,timeout);
|
nn.timeoutid = window.setTimeout(nn.close,timeout);
|
||||||
} else {
|
} else {
|
||||||
window.clearTimeout(nn.timeoutid);
|
window.clearTimeout(nn.timeoutid);
|
||||||
}
|
}
|
||||||
|
if (nn.hidden) {
|
||||||
|
nn.showNotification();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -138,7 +199,45 @@ RED.notify = (function() {
|
|||||||
n.timeoutid = window.setTimeout(n.close,timeout||5000);
|
n.timeoutid = window.setTimeout(n.close,timeout||5000);
|
||||||
}
|
}
|
||||||
currentNotifications.push(n);
|
currentNotifications.push(n);
|
||||||
|
if (options.id) {
|
||||||
|
persistentNotifications[options.id] = n;
|
||||||
|
notificationButtonWrapper.show();
|
||||||
|
}
|
||||||
c+=1;
|
c+=1;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RED.notify = notify;
|
||||||
|
|
||||||
|
|
||||||
|
function hidePersistent() {
|
||||||
|
for(var i in persistentNotifications) {
|
||||||
|
if (persistentNotifications.hasOwnProperty(i)) {
|
||||||
|
persistentNotifications[i].hideNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function showPersistent() {
|
||||||
|
for(var i in persistentNotifications) {
|
||||||
|
if (persistentNotifications.hasOwnProperty(i)) {
|
||||||
|
persistentNotifications[i].showNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationButtonWrapper;
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function() {
|
||||||
|
notificationButtonWrapper = $('<li>'+
|
||||||
|
'<a id="btn-notifications" class="button" href="#">'+
|
||||||
|
'<i class="fa fa-warning"></i>'+
|
||||||
|
'</a>'+
|
||||||
|
'</li>').prependTo(".header-toolbar").hide();
|
||||||
|
$('#btn-notifications').click(function() {
|
||||||
|
showPersistent();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
notify: notify
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -75,27 +75,21 @@ RED.palette.editor = (function() {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function installNodeModule(id,version,shade,callback) {
|
function installNodeModule(id,version,callback) {
|
||||||
var requestBody = {
|
var requestBody = {
|
||||||
module: id
|
module: id
|
||||||
};
|
};
|
||||||
if (callback === undefined) {
|
if (version) {
|
||||||
callback = shade;
|
|
||||||
shade = version;
|
|
||||||
} else {
|
|
||||||
requestBody.version = version;
|
requestBody.version = version;
|
||||||
}
|
}
|
||||||
shade.show();
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url:"nodes",
|
url:"nodes",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: JSON.stringify(requestBody),
|
data: JSON.stringify(requestBody),
|
||||||
contentType: "application/json; charset=utf-8"
|
contentType: "application/json; charset=utf-8"
|
||||||
}).done(function(data,textStatus,xhr) {
|
}).done(function(data,textStatus,xhr) {
|
||||||
shade.hide();
|
|
||||||
callback();
|
callback();
|
||||||
}).fail(function(xhr,textStatus,err) {
|
}).fail(function(xhr,textStatus,err) {
|
||||||
shade.hide();
|
|
||||||
callback(xhr);
|
callback(xhr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -604,24 +598,7 @@ RED.palette.editor = (function() {
|
|||||||
if ($(this).hasClass('disabled')) {
|
if ($(this).hasClass('disabled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$("#palette-module-install-confirm").data('module',entry.name);
|
update(entry,loadedIndex[entry.name].version,container,function(err){});
|
||||||
$("#palette-module-install-confirm").data('version',loadedIndex[entry.name].version);
|
|
||||||
$("#palette-module-install-confirm").data('shade',shade);
|
|
||||||
|
|
||||||
$("#palette-module-install-confirm-body").html(entry.local?
|
|
||||||
RED._("palette.editor.confirm.update.body"):
|
|
||||||
RED._("palette.editor.confirm.cannotUpdate.body")
|
|
||||||
);
|
|
||||||
$(".palette-module-install-confirm-button-install").hide();
|
|
||||||
$(".palette-module-install-confirm-button-remove").hide();
|
|
||||||
if (entry.local) {
|
|
||||||
$(".palette-module-install-confirm-button-update").show();
|
|
||||||
} else {
|
|
||||||
$(".palette-module-install-confirm-button-update").hide();
|
|
||||||
}
|
|
||||||
$("#palette-module-install-confirm")
|
|
||||||
.dialog('option', 'title',RED._("palette.editor.confirm.update.title"))
|
|
||||||
.dialog('open');
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -629,16 +606,7 @@ RED.palette.editor = (function() {
|
|||||||
removeButton.attr('id','up_'+Math.floor(Math.random()*1000000000));
|
removeButton.attr('id','up_'+Math.floor(Math.random()*1000000000));
|
||||||
removeButton.click(function(evt) {
|
removeButton.click(function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
remove(entry,container,function(err){});
|
||||||
$("#palette-module-install-confirm").data('module',entry.name);
|
|
||||||
$("#palette-module-install-confirm").data('shade',shade);
|
|
||||||
$("#palette-module-install-confirm-body").html(RED._("palette.editor.confirm.remove.body"));
|
|
||||||
$(".palette-module-install-confirm-button-install").hide();
|
|
||||||
$(".palette-module-install-confirm-button-remove").show();
|
|
||||||
$(".palette-module-install-confirm-button-update").hide();
|
|
||||||
$("#palette-module-install-confirm")
|
|
||||||
.dialog('option', 'title', RED._("palette.editor.confirm.remove.title"))
|
|
||||||
.dialog('open');
|
|
||||||
})
|
})
|
||||||
if (!entry.local) {
|
if (!entry.local) {
|
||||||
removeButton.hide();
|
removeButton.hide();
|
||||||
@ -836,22 +804,11 @@ RED.palette.editor = (function() {
|
|||||||
$('<span class="palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</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 buttonRow = $('<div>',{class:"palette-module-meta"}).appendTo(headerRow);
|
||||||
var buttonGroup = $('<div>',{class:"palette-module-button-group"}).appendTo(buttonRow);
|
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);
|
var installButton = $('<a href="#" class="editor-button editor-button-small"></a>').html(RED._('palette.editor.install')).appendTo(buttonGroup);
|
||||||
installButton.click(function(e) {
|
installButton.click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!$(this).hasClass('disabled')) {
|
if (!$(this).hasClass('disabled')) {
|
||||||
$("#palette-module-install-confirm").data('module',entry.id);
|
install(entry,container,function(xhr) {});
|
||||||
$("#palette-module-install-confirm").data('version',entry.version);
|
|
||||||
$("#palette-module-install-confirm").data('url',entry.url);
|
|
||||||
$("#palette-module-install-confirm").data('shade',shade);
|
|
||||||
$("#palette-module-install-confirm-body").html(RED._("palette.editor.confirm.install.body"));
|
|
||||||
$(".palette-module-install-confirm-button-install").show();
|
|
||||||
$(".palette-module-install-confirm-button-remove").hide();
|
|
||||||
$(".palette-module-install-confirm-button-update").hide();
|
|
||||||
$("#palette-module-install-confirm")
|
|
||||||
.dialog('option', 'title', RED._("palette.editor.confirm.install.title"))
|
|
||||||
.dialog('open');
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (nodeEntries.hasOwnProperty(entry.id)) {
|
if (nodeEntries.hasOwnProperty(entry.id)) {
|
||||||
@ -869,88 +826,126 @@ RED.palette.editor = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('<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);
|
$('<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);
|
||||||
|
}
|
||||||
$('<div id="palette-module-install-confirm" class="hide"><form class="form-horizontal"><div id="palette-module-install-confirm-body" class="node-dialog-confirm-row"></div></form></div>').appendTo(document.body);
|
function update(entry,version,container,done) {
|
||||||
$("#palette-module-install-confirm").dialog({
|
if (RED.settings.theme('palette.editable') === false) {
|
||||||
title: RED._('palette.editor.confirm.title'),
|
done(new Error('Palette not editable'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var notification = RED.notify(RED._("palette.editor.confirm.update.body",{module:entry.name}),{
|
||||||
modal: true,
|
modal: true,
|
||||||
autoOpen: false,
|
fixed: true,
|
||||||
width: 550,
|
|
||||||
height: "auto",
|
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: RED._("common.label.cancel"),
|
text: RED._("common.label.cancel"),
|
||||||
click: function() {
|
click: function() {
|
||||||
$( this ).dialog( "close" );
|
notification.close();
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: RED._("palette.editor.confirm.button.review"),
|
|
||||||
class: "primary palette-module-install-confirm-button-install",
|
|
||||||
click: function() {
|
|
||||||
var url = $(this).data('url');
|
|
||||||
window.open(url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: RED._("palette.editor.confirm.button.install"),
|
|
||||||
class: "primary palette-module-install-confirm-button-install",
|
|
||||||
click: function() {
|
|
||||||
var id = $(this).data('module');
|
|
||||||
var version = $(this).data('version');
|
|
||||||
var shade = $(this).data('shade');
|
|
||||||
installNodeModule(id,version,shade,function(xhr) {
|
|
||||||
if (xhr) {
|
|
||||||
if (xhr.responseJSON) {
|
|
||||||
RED.notify(RED._('palette.editor.errors.installFailed',{module: id,message:xhr.responseJSON.message}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: RED._("palette.editor.confirm.button.remove"),
|
|
||||||
class: "primary palette-module-install-confirm-button-remove",
|
|
||||||
click: function() {
|
|
||||||
var id = $(this).data('module');
|
|
||||||
var shade = $(this).data('shade');
|
|
||||||
shade.show();
|
|
||||||
removeNodeModule(id, function(xhr) {
|
|
||||||
shade.hide();
|
|
||||||
if (xhr) {
|
|
||||||
if (xhr.responseJSON) {
|
|
||||||
RED.notify(RED._('palette.editor.errors.removeFailed',{module: id,message:xhr.responseJSON.message}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$( this ).dialog( "close" );
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: RED._("palette.editor.confirm.button.update"),
|
text: RED._("palette.editor.confirm.button.update"),
|
||||||
class: "primary palette-module-install-confirm-button-update",
|
class: "primary palette-module-install-confirm-button-update",
|
||||||
click: function() {
|
click: function() {
|
||||||
var id = $(this).data('module');
|
var spinner = RED.utils.addSpinnerOverlay(container, true);
|
||||||
var version = $(this).data('version');
|
installNodeModule(entry.name,version,function(xhr) {
|
||||||
var shade = $(this).data('shade');
|
spinner.remove();
|
||||||
shade.show();
|
if (xhr) {
|
||||||
installNodeModule(id,version,shade,function(xhr) {
|
if (xhr.responseJSON) {
|
||||||
if (xhr) {
|
RED.notify(RED._('palette.editor.errors.updateFailed',{module: entry.name,message:xhr.responseJSON.message}));
|
||||||
if (xhr.responseJSON) {
|
}
|
||||||
RED.notify(RED._('palette.editor.errors.updateFailed',{module: id,message:xhr.responseJSON.message}));
|
}
|
||||||
}
|
done(xhr);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
$( this ).dialog( "close" );
|
notification.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function remove(entry,container,done) {
|
||||||
|
if (RED.settings.theme('palette.editable') === false) {
|
||||||
|
done(new Error('Palette not editable'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var notification = RED.notify(RED._("palette.editor.confirm.remove.body",{module:entry.name}),{
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: RED._("palette.editor.confirm.button.remove"),
|
||||||
|
class: "primary palette-module-install-confirm-button-remove",
|
||||||
|
click: function() {
|
||||||
|
var spinner = RED.utils.addSpinnerOverlay(container, true);
|
||||||
|
removeNodeModule(entry.name, function(xhr) {
|
||||||
|
spinner.remove();
|
||||||
|
if (xhr) {
|
||||||
|
if (xhr.responseJSON) {
|
||||||
|
RED.notify(RED._('palette.editor.errors.removeFailed',{module: entry.name,message:xhr.responseJSON.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function install(entry,container,done) {
|
||||||
|
if (RED.settings.theme('palette.editable') === false) {
|
||||||
|
done(new Error('Palette not editable'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var buttons = [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (entry.url) {
|
||||||
|
buttons.push({
|
||||||
|
text: RED._("palette.editor.confirm.button.review"),
|
||||||
|
class: "primary palette-module-install-confirm-button-install",
|
||||||
|
click: function() {
|
||||||
|
var url = entry.url||"";
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buttons.push({
|
||||||
|
text: RED._("palette.editor.confirm.button.install"),
|
||||||
|
class: "primary palette-module-install-confirm-button-install",
|
||||||
|
click: function() {
|
||||||
|
var spinner = RED.utils.addSpinnerOverlay(container, true);
|
||||||
|
installNodeModule(entry.id,entry.version,function(xhr) {
|
||||||
|
spinner.remove();
|
||||||
|
if (xhr) {
|
||||||
|
if (xhr.responseJSON) {
|
||||||
|
RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done(xhr);
|
||||||
|
});
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var notification = RED.notify(RED._("palette.editor.confirm.install.body",{module:entry.id}),{
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons: buttons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init
|
init: init,
|
||||||
|
install: install
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -206,11 +206,11 @@ RED.palette = (function() {
|
|||||||
RED.view.focus();
|
RED.view.focus();
|
||||||
var helpText;
|
var helpText;
|
||||||
if (nt.indexOf("subflow:") === 0) {
|
if (nt.indexOf("subflow:") === 0) {
|
||||||
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"");
|
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
|
||||||
} else {
|
} else {
|
||||||
helpText = $("script[data-help-name='"+d.type+"']").html()||"";
|
helpText = $("script[data-help-name='"+d.type+"']").html()||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
|
||||||
}
|
}
|
||||||
RED.sidebar.info.set(helpText);
|
RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp"));
|
||||||
});
|
});
|
||||||
var chart = $("#chart");
|
var chart = $("#chart");
|
||||||
var chartOffset = chart.offset();
|
var chartOffset = chart.offset();
|
||||||
|
1493
editor/js/ui/projects/projectSettings.js
Normal file
1493
editor/js/ui/projects/projectSettings.js
Normal file
File diff suppressed because it is too large
Load Diff
418
editor/js/ui/projects/projectUserSettings.js
Normal file
418
editor/js/ui/projects/projectUserSettings.js
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
RED.projects.userSettings = (function() {
|
||||||
|
|
||||||
|
var gitUsernameInput;
|
||||||
|
var gitEmailInput;
|
||||||
|
|
||||||
|
function createGitUserSection(pane) {
|
||||||
|
|
||||||
|
var currentGitSettings = RED.settings.get('git') || {};
|
||||||
|
currentGitSettings.user = currentGitSettings.user || {};
|
||||||
|
|
||||||
|
var title = $('<h3></h3>').text("Committer Details").appendTo(pane);
|
||||||
|
|
||||||
|
var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
|
||||||
|
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text("Leave blank to use system default");
|
||||||
|
|
||||||
|
var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
|
||||||
|
$('<label for=""></label>').text('Username').appendTo(row);
|
||||||
|
gitUsernameInput = $('<input type="text">').appendTo(row);
|
||||||
|
gitUsernameInput.val(currentGitSettings.user.name||"");
|
||||||
|
|
||||||
|
row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
|
||||||
|
$('<label for=""></label>').text('Email').appendTo(row);
|
||||||
|
gitEmailInput = $('<input type="text">').appendTo(row);
|
||||||
|
gitEmailInput.val(currentGitSettings.user.email||"");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createSSHKeySection(pane) {
|
||||||
|
var container = $('<div class="user-settings-section"></div>').appendTo(pane);
|
||||||
|
var popover;
|
||||||
|
var title = $('<h3></h3>').text("SSH Keys").appendTo(container);
|
||||||
|
var subtitle = $('<div style="color:#aaa;"></div>').appendTo(container).text("Allows you to create secure connections to remote git repositories.");
|
||||||
|
|
||||||
|
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="editor-button editor-button-small" style="float: right; margin-right: 10px;">add key</button>')
|
||||||
|
.appendTo(subtitle)
|
||||||
|
.click(function(evt) {
|
||||||
|
addKeyButton.attr('disabled',true);
|
||||||
|
saveButton.attr('disabled',true);
|
||||||
|
// bg.children().removeClass("selected");
|
||||||
|
// addLocalButton.click();
|
||||||
|
addKeyDialog.slideDown(200);
|
||||||
|
keyNameInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
var validateForm = function() {
|
||||||
|
var valid = /^[a-zA-Z0-9\-_]+$/.test(keyNameInput.val());
|
||||||
|
keyNameInput.toggleClass('input-error',keyNameInputChanged&&!valid);
|
||||||
|
|
||||||
|
// var selectedButton = bg.find(".selected");
|
||||||
|
// if (selectedButton[0] === addLocalButton[0]) {
|
||||||
|
// valid = valid && localPublicKeyPathInput.val().length > 0 && localPrivateKeyPathInput.val().length > 0;
|
||||||
|
// } else if (selectedButton[0] === uploadButton[0]) {
|
||||||
|
// valid = valid && publicKeyInput.val().length > 0 && privateKeyInput.val().length > 0;
|
||||||
|
// } else if (selectedButton[0] === generateButton[0]) {
|
||||||
|
var passphrase = passphraseInput.val();
|
||||||
|
var validPassphrase = passphrase.length === 0 || passphrase.length >= 8;
|
||||||
|
passphraseInput.toggleClass('input-error',!validPassphrase);
|
||||||
|
if (!validPassphrase) {
|
||||||
|
passphraseInputSubLabel.text("Passphrase too short");
|
||||||
|
} else if (passphrase.length === 0) {
|
||||||
|
passphraseInputSubLabel.text("Optional");
|
||||||
|
} else {
|
||||||
|
passphraseInputSubLabel.text("");
|
||||||
|
}
|
||||||
|
valid = valid && validPassphrase;
|
||||||
|
// }
|
||||||
|
|
||||||
|
saveButton.attr('disabled',!valid);
|
||||||
|
|
||||||
|
if (popover) {
|
||||||
|
popover.close();
|
||||||
|
popover = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var row = $('<div class="user-settings-row"></div>').appendTo(container);
|
||||||
|
var addKeyDialog = $('<div class="projects-dialog-list-dialog"></div>').hide().appendTo(row);
|
||||||
|
$('<div class="projects-dialog-list-dialog-header">').text('Add SSH Key').appendTo(addKeyDialog);
|
||||||
|
var addKeyDialogBody = $('<div>').appendTo(addKeyDialog);
|
||||||
|
|
||||||
|
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
|
||||||
|
$('<div style="color:#aaa;"></div>').appendTo(row).text("Generate a new public/private key pair");
|
||||||
|
// var bg = $('<div></div>',{class:"button-group", style:"text-align: center"}).appendTo(row);
|
||||||
|
// var addLocalButton = $('<button class="editor-button toggle selected">use local key</button>').appendTo(bg);
|
||||||
|
// var uploadButton = $('<button class="editor-button toggle">upload key</button>').appendTo(bg);
|
||||||
|
// var generateButton = $('<button class="editor-button toggle">generate key</button>').appendTo(bg);
|
||||||
|
// bg.children().click(function(e) {
|
||||||
|
// e.preventDefault();
|
||||||
|
// if ($(this).hasClass("selected")) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// bg.children().removeClass("selected");
|
||||||
|
// $(this).addClass("selected");
|
||||||
|
// if (this === addLocalButton[0]) {
|
||||||
|
// addLocalKeyPane.show();
|
||||||
|
// generateKeyPane.hide();
|
||||||
|
// uploadKeyPane.hide();
|
||||||
|
// } else if (this === uploadButton[0]) {
|
||||||
|
// addLocalKeyPane.hide();
|
||||||
|
// generateKeyPane.hide();
|
||||||
|
// uploadKeyPane.show();
|
||||||
|
// } else if (this === generateButton[0]){
|
||||||
|
// addLocalKeyPane.hide();
|
||||||
|
// generateKeyPane.show();
|
||||||
|
// uploadKeyPane.hide();
|
||||||
|
// }
|
||||||
|
// validateForm();
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
|
||||||
|
$('<label for=""></label>').text('Name').appendTo(row);
|
||||||
|
var keyNameInputChanged = false;
|
||||||
|
var keyNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
|
||||||
|
keyNameInputChanged = true;
|
||||||
|
validateForm();
|
||||||
|
});
|
||||||
|
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
|
||||||
|
|
||||||
|
var generateKeyPane = $('<div>').appendTo(addKeyDialogBody);
|
||||||
|
row = $('<div class="user-settings-row"></div>').appendTo(generateKeyPane);
|
||||||
|
$('<label for=""></label>').text('Passphrase').appendTo(row);
|
||||||
|
var passphraseInput = $('<input type="password">').appendTo(row).on("change keyup paste",validateForm);
|
||||||
|
var passphraseInputSubLabel = $('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row).find("small");
|
||||||
|
|
||||||
|
// var addLocalKeyPane = $('<div>').hide().appendTo(addKeyDialogBody);
|
||||||
|
// row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane);
|
||||||
|
// $('<label for=""></label>').text('Public key').appendTo(row);
|
||||||
|
// var localPublicKeyPathInput = $('<input type="text">').appendTo(row).on("change keyup paste",validateForm);
|
||||||
|
// $('<label class="projects-edit-form-sublabel"><small>Public key file path, for example: ~/.ssh/id_rsa.pub</small></label>').appendTo(row).find("small");
|
||||||
|
// row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane);
|
||||||
|
// $('<label for=""></label>').text('Private key').appendTo(row);
|
||||||
|
// var localPrivateKeyPathInput = $('<input type="text">').appendTo(row).on("change keyup paste",validateForm);
|
||||||
|
// $('<label class="projects-edit-form-sublabel"><small>Private key file path, for example: ~/.ssh/id_rsa</small></label>').appendTo(row).find("small");
|
||||||
|
//
|
||||||
|
// var uploadKeyPane = $('<div>').hide().appendTo(addKeyDialogBody);
|
||||||
|
// row = $('<div class="user-settings-row"></div>').appendTo(uploadKeyPane);
|
||||||
|
// $('<label for=""></label>').text('Public key').appendTo(row);
|
||||||
|
// var publicKeyInput = $('<textarea>').appendTo(row).on("change keyup paste",validateForm);
|
||||||
|
// $('<label class="projects-edit-form-sublabel"><small>Paste in public key contents, for example: ~/.ssh/id_rsa.pub</small></label>').appendTo(row).find("small");
|
||||||
|
// row = $('<div class="user-settings-row"></div>').appendTo(uploadKeyPane);
|
||||||
|
// $('<label for=""></label>').text('Private key').appendTo(row);
|
||||||
|
// var privateKeyInput = $('<textarea>').appendTo(row).on("change keyup paste",validateForm);
|
||||||
|
// $('<label class="projects-edit-form-sublabel"><small>Paste in private key contents, for example: ~/.ssh/id_rsa</small></label>').appendTo(row).find("small");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var hideEditForm = function() {
|
||||||
|
addKeyButton.attr('disabled',false);
|
||||||
|
addKeyDialog.hide();
|
||||||
|
|
||||||
|
keyNameInput.val("");
|
||||||
|
keyNameInputChanged = false;
|
||||||
|
passphraseInput.val("");
|
||||||
|
// localPublicKeyPathInput.val("");
|
||||||
|
// localPrivateKeyPathInput.val("");
|
||||||
|
// publicKeyInput.val("");
|
||||||
|
// privateKeyInput.val("");
|
||||||
|
if (popover) {
|
||||||
|
popover.close();
|
||||||
|
popover = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(addKeyDialog);
|
||||||
|
$('<button class="editor-button">Cancel</button>')
|
||||||
|
.appendTo(formButtons)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
hideEditForm();
|
||||||
|
});
|
||||||
|
var saveButton = $('<button class="editor-button">Generate key</button>')
|
||||||
|
.appendTo(formButtons)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var spinner = utils.addSpinnerOverlay(addKeyDialog).addClass('projects-dialog-spinner-contain');
|
||||||
|
var payload = {
|
||||||
|
name: keyNameInput.val()
|
||||||
|
};
|
||||||
|
|
||||||
|
// var selectedButton = bg.find(".selected");
|
||||||
|
// if (selectedButton[0] === addLocalButton[0]) {
|
||||||
|
// payload.type = "local";
|
||||||
|
// payload.publicKeyPath = localPublicKeyPathInput.val();
|
||||||
|
// payload.privateKeyPath = localPrivateKeyPathInput.val();
|
||||||
|
// } else if (selectedButton[0] === uploadButton[0]) {
|
||||||
|
// payload.type = "upload";
|
||||||
|
// payload.publicKey = publicKeyInput.val();
|
||||||
|
// payload.privateKey = privateKeyInput.val();
|
||||||
|
// } else if (selectedButton[0] === generateButton[0]) {
|
||||||
|
payload.type = "generate";
|
||||||
|
payload.comment = gitEmailInput.val();
|
||||||
|
payload.password = passphraseInput.val();
|
||||||
|
payload.size = 4096;
|
||||||
|
// }
|
||||||
|
var done = function(err) {
|
||||||
|
spinner.remove();
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hideEditForm();
|
||||||
|
}
|
||||||
|
// console.log(JSON.stringify(payload,null,4));
|
||||||
|
RED.deploy.setDeployInflight(true);
|
||||||
|
utils.sendRequest({
|
||||||
|
url: "settings/user/keys",
|
||||||
|
type: "POST",
|
||||||
|
responses: {
|
||||||
|
0: function(error) {
|
||||||
|
done(error);
|
||||||
|
},
|
||||||
|
200: function(data) {
|
||||||
|
refreshSSHKeyList(payload.name);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'unexpected_error': function(error) {
|
||||||
|
console.log(error);
|
||||||
|
done(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
row = $('<div class="user-settings-row projects-dialog-list"></div>').appendTo(container);
|
||||||
|
var emptyItem = { empty: true };
|
||||||
|
var expandKey = function(container,entry) {
|
||||||
|
var row = $('<div class="projects-dialog-ssh-public-key">',{style:"position:relative"}).appendTo(container);
|
||||||
|
var keyBox = $('<pre>',{style:"min-height: 80px"}).appendTo(row);
|
||||||
|
var spinner = utils.addSpinnerOverlay(keyBox).addClass('projects-dialog-spinner-contain');
|
||||||
|
var options = {
|
||||||
|
url: 'settings/user/keys/'+entry.name,
|
||||||
|
type: "GET",
|
||||||
|
responses: {
|
||||||
|
200: function(data) {
|
||||||
|
keyBox.text(data.publickey);
|
||||||
|
spinner.remove();
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'unexpected_error': function(error) {
|
||||||
|
console.log(error);
|
||||||
|
spinner.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
utils.sendRequest(options);
|
||||||
|
|
||||||
|
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(row);
|
||||||
|
$('<button class="editor-button editor-button-small">Copy public key to clipboard</button>')
|
||||||
|
.appendTo(formButtons)
|
||||||
|
.click(function(evt) {
|
||||||
|
try {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
document.getSelection().selectAllChildren(keyBox[0]);
|
||||||
|
var ret = document.execCommand('copy');
|
||||||
|
document.getSelection().empty();
|
||||||
|
} catch(err) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
var keyList = $('<ol class="projects-dialog-ssh-key-list">').appendTo(row).editableList({
|
||||||
|
height: 'auto',
|
||||||
|
addButton: false,
|
||||||
|
scrollOnAdd: false,
|
||||||
|
addItem: function(row,index,entry) {
|
||||||
|
var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
|
||||||
|
if (entry.empty) {
|
||||||
|
container.addClass('red-ui-search-empty');
|
||||||
|
container.text("No SSH keys");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var topRow = $('<div class="projects-dialog-ssh-key-header">').appendTo(container);
|
||||||
|
$('<span class="entry-icon"><i class="fa fa-key"></i></span>').appendTo(topRow);
|
||||||
|
$('<span class="entry-name">').text(entry.name).appendTo(topRow);
|
||||||
|
var tools = $('<span class="button-row entry-tools">').appendTo(topRow);
|
||||||
|
var expandedRow;
|
||||||
|
topRow.click(function(e) {
|
||||||
|
if (expandedRow) {
|
||||||
|
expandedRow.slideUp(200,function() {
|
||||||
|
expandedRow.remove();
|
||||||
|
expandedRow = null;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
expandedRow = expandKey(container,entry);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!entry.system) {
|
||||||
|
$('<button class="editor-button editor-button-small"><i class="fa fa-trash"></i></button>')
|
||||||
|
.appendTo(tools)
|
||||||
|
.click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
|
||||||
|
var notification = RED.notify("Are you sure you want to delete the SSH key '"+entry.name+"'? This cannot be undone.", {
|
||||||
|
type: 'warning',
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
spinner.remove();
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Delete key",
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
var url = "settings/user/keys/"+entry.name;
|
||||||
|
var options = {
|
||||||
|
url: url,
|
||||||
|
type: "DELETE",
|
||||||
|
responses: {
|
||||||
|
200: function(data) {
|
||||||
|
row.fadeOut(200,function() {
|
||||||
|
keyList.editableList('removeItem',entry);
|
||||||
|
setTimeout(spinner.remove, 100);
|
||||||
|
if (keyList.editableList('length') === 0) {
|
||||||
|
keyList.editableList('addItem',emptyItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'unexpected_error': function(error) {
|
||||||
|
console.log(error);
|
||||||
|
spinner.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
utils.sendRequest(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (entry.expand) {
|
||||||
|
expandedRow = expandKey(container,entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var refreshSSHKeyList = function(justAdded) {
|
||||||
|
$.getJSON("settings/user/keys",function(result) {
|
||||||
|
if (result.keys) {
|
||||||
|
result.keys.sort(function(A,B) {
|
||||||
|
return A.name.localeCompare(B.name);
|
||||||
|
});
|
||||||
|
keyList.editableList('empty');
|
||||||
|
result.keys.forEach(function(key) {
|
||||||
|
if (key.name === justAdded) {
|
||||||
|
key.expand = true;
|
||||||
|
}
|
||||||
|
keyList.editableList('addItem',key);
|
||||||
|
});
|
||||||
|
if (keyList.editableList('length') === 0) {
|
||||||
|
keyList.editableList('addItem',emptyItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refreshSSHKeyList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSettingsPane(activeProject) {
|
||||||
|
var pane = $('<div id="user-settings-tab-gitconfig" class="project-settings-tab-pane node-help"></div>');
|
||||||
|
createGitUserSection(pane);
|
||||||
|
createSSHKeySection(pane);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
var utils;
|
||||||
|
function init(_utils) {
|
||||||
|
utils = _utils;
|
||||||
|
RED.userSettings.add({
|
||||||
|
id:'gitconfig',
|
||||||
|
title: "Git config", // TODO: nls
|
||||||
|
get: createSettingsPane,
|
||||||
|
close: function() {
|
||||||
|
var currentGitSettings = RED.settings.get('git') || {};
|
||||||
|
currentGitSettings.user = currentGitSettings.user || {};
|
||||||
|
currentGitSettings.user.name = gitUsernameInput.val();
|
||||||
|
currentGitSettings.user.email = gitEmailInput.val();
|
||||||
|
RED.settings.set('git', currentGitSettings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
};
|
||||||
|
})();
|
1879
editor/js/ui/projects/projects.js
Normal file
1879
editor/js/ui/projects/projects.js
Normal file
File diff suppressed because it is too large
Load Diff
1284
editor/js/ui/projects/tab-versionControl.js
Normal file
1284
editor/js/ui/projects/tab-versionControl.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -35,7 +35,8 @@ RED.sidebar = (function() {
|
|||||||
tab.onremove.call(tab);
|
tab.onremove.call(tab);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
minimumActiveTabWidth: 110
|
minimumActiveTabWidth: 70
|
||||||
|
// scrollable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
var knownTabs = {
|
var knownTabs = {
|
||||||
|
@ -50,13 +50,15 @@ RED.sidebar.info = (function() {
|
|||||||
}).hide();
|
}).hide();
|
||||||
|
|
||||||
nodeSection = sections.add({
|
nodeSection = sections.add({
|
||||||
title: RED._("sidebar.info.node"),
|
title: RED._("sidebar.info.info"),
|
||||||
collapsible: false
|
collapsible: true
|
||||||
});
|
});
|
||||||
|
nodeSection.expand();
|
||||||
infoSection = sections.add({
|
infoSection = sections.add({
|
||||||
title: RED._("sidebar.info.information"),
|
title: RED._("sidebar.info.nodeHelp"),
|
||||||
collapsible: false
|
collapsible: true
|
||||||
});
|
});
|
||||||
|
infoSection.expand();
|
||||||
infoSection.content.css("padding","6px");
|
infoSection.content.css("padding","6px");
|
||||||
infoSection.container.css("border-bottom","none");
|
infoSection.container.css("border-bottom","none");
|
||||||
|
|
||||||
@ -96,23 +98,7 @@ RED.sidebar.info = (function() {
|
|||||||
RED.sidebar.show("info");
|
RED.sidebar.show("info");
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonFilter(key,value) {
|
// TODO: DRY - projects.js
|
||||||
if (key === "") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
var t = typeof value;
|
|
||||||
if ($.isArray(value)) {
|
|
||||||
return "[array:"+value.length+"]";
|
|
||||||
} else if (t === "object") {
|
|
||||||
return "[object]"
|
|
||||||
} else if (t === "string") {
|
|
||||||
if (value.length > 30) {
|
|
||||||
return value.substring(0,30)+" ...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTargetToExternalLinks(el) {
|
function addTargetToExternalLinks(el) {
|
||||||
$(el).find("a").each(function(el) {
|
$(el).find("a").each(function(el) {
|
||||||
var href = $(this).attr('href');
|
var href = $(this).attr('href');
|
||||||
@ -123,76 +109,42 @@ RED.sidebar.info = (function() {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
function refresh(node) {
|
function refresh(node) {
|
||||||
|
if (node === undefined) {
|
||||||
|
refreshSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
sections.show();
|
sections.show();
|
||||||
$(nodeSection.content).empty();
|
$(nodeSection.content).empty();
|
||||||
$(infoSection.content).empty();
|
$(infoSection.content).empty();
|
||||||
|
|
||||||
var table = $('<table class="node-info"></table>');
|
|
||||||
var tableBody = $('<tbody>').appendTo(table);
|
|
||||||
var propRow;
|
var propRow;
|
||||||
|
|
||||||
|
var table = $('<table class="node-info"></table>').appendTo(nodeSection.content);
|
||||||
|
var tableBody = $('<tbody>').appendTo(table);
|
||||||
var subflowNode;
|
var subflowNode;
|
||||||
if (node.type === "tab") {
|
var subflowUserCount;
|
||||||
nodeSection.title.html(RED._("sidebar.info.flow"));
|
|
||||||
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.tabName")+'</td><td></td></tr>').appendTo(tableBody);
|
var activeProject = RED.projects.getActiveProject();
|
||||||
$(propRow.children()[1]).html(' '+(node.label||""))
|
if (activeProject) {
|
||||||
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.id")+"</td><td></td></tr>").appendTo(tableBody);
|
propRow = $('<tr class="node-info-node-row"><td>Project</td><td></td></tr>').appendTo(tableBody);
|
||||||
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
|
$(propRow.children()[1]).text(activeProject.name||"");
|
||||||
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
|
$('<tr class="node-info-property-expand blank"><td colspan="2"></td></tr>').appendTo(tableBody);
|
||||||
$(propRow.children()[1]).html((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
|
$('<button class="editor-button editor-button-small" style="position:absolute;right:2px;"><i class="fa fa-ellipsis-h"></i></button>')
|
||||||
|
.appendTo(propRow.children()[1])
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
RED.projects.editProject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
infoSection.container.show();
|
||||||
|
if (node === null) {
|
||||||
|
return;
|
||||||
|
} else if (Array.isArray(node)) {
|
||||||
|
infoSection.container.hide();
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.selection")+"</td><td></td></tr>").appendTo(tableBody);
|
||||||
|
$(propRow.children()[1]).text(RED._("sidebar.info.nodes",{count:node.length}))
|
||||||
} else {
|
} else {
|
||||||
nodeSection.title.html(RED._("sidebar.info.node"));
|
|
||||||
if (node.type !== "subflow" && node.name) {
|
|
||||||
$('<tr class="node-info-node-row"><td>'+RED._("common.label.name")+'</td><td> <span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'">'+node.name+'</span></td></tr>').appendTo(tableBody);
|
|
||||||
}
|
|
||||||
$('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.type")+"</td><td> "+node.type+"</td></tr>").appendTo(tableBody);
|
|
||||||
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.id")+"</td><td></td></tr>").appendTo(tableBody);
|
|
||||||
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
|
|
||||||
|
|
||||||
var m = /^subflow(:(.+))?$/.exec(node.type);
|
var m = /^subflow(:(.+))?$/.exec(node.type);
|
||||||
|
|
||||||
if (!m && node.type != "subflow" && node.type != "comment") {
|
|
||||||
if (node._def) {
|
|
||||||
var count = 0;
|
|
||||||
var defaults = node._def.defaults;
|
|
||||||
for (var n in defaults) {
|
|
||||||
if (n != "name" && defaults.hasOwnProperty(n)) {
|
|
||||||
var val = node[n];
|
|
||||||
var type = typeof val;
|
|
||||||
count++;
|
|
||||||
propRow = $('<tr class="node-info-property-row'+(expandedSections.property?"":" hide")+'"><td>'+n+"</td><td></td></tr>").appendTo(tableBody);
|
|
||||||
if (defaults[n].type) {
|
|
||||||
var configNode = RED.nodes.node(val);
|
|
||||||
if (!configNode) {
|
|
||||||
RED.utils.createObjectElement(undefined).appendTo(propRow.children()[1]);
|
|
||||||
} else {
|
|
||||||
var configLabel = RED.utils.getNodeLabel(configNode,val);
|
|
||||||
var container = propRow.children()[1];
|
|
||||||
|
|
||||||
var div = $('<span>',{class:""}).appendTo(container);
|
|
||||||
var nodeDiv = $('<div>',{class:"palette_node palette_node_small"}).appendTo(div);
|
|
||||||
var colour = configNode._def.color;
|
|
||||||
var icon_url = RED.utils.getNodeIcon(configNode._def);
|
|
||||||
nodeDiv.css({'backgroundColor':colour, "cursor":"pointer"});
|
|
||||||
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
|
|
||||||
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
|
|
||||||
var nodeContainer = $('<span></span>').css({"verticalAlign":"top","marginLeft":"6px"}).html(configLabel).appendTo(container);
|
|
||||||
|
|
||||||
nodeDiv.on('dblclick',function() {
|
|
||||||
RED.editor.editConfig("", configNode.type, configNode.id);
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
RED.utils.createObjectElement(val).appendTo(propRow.children()[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count > 0) {
|
|
||||||
$('<tr class="node-info-property-expand blank"><td colspan="2"><a href="#" class=" node-info-property-header'+(expandedSections.property?" expanded":"")+'"><span class="node-info-property-show-more">'+RED._("sidebar.info.showMore")+'</span><span class="node-info-property-show-less">'+RED._("sidebar.info.showLess")+'</span> <i class="fa fa-caret-down"></i></a></td></tr>').appendTo(tableBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m) {
|
if (m) {
|
||||||
if (m[2]) {
|
if (m[2]) {
|
||||||
subflowNode = RED.nodes.subflow(m[2]);
|
subflowNode = RED.nodes.subflow(m[2]);
|
||||||
@ -200,49 +152,120 @@ RED.sidebar.info = (function() {
|
|||||||
subflowNode = node;
|
subflowNode = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('<tr class="blank"><th colspan="2">'+RED._("sidebar.info.subflow")+'</th></tr>').appendTo(tableBody);
|
subflowUserCount = 0;
|
||||||
|
|
||||||
var userCount = 0;
|
|
||||||
var subflowType = "subflow:"+subflowNode.id;
|
var subflowType = "subflow:"+subflowNode.id;
|
||||||
RED.nodes.eachNode(function(n) {
|
RED.nodes.eachNode(function(n) {
|
||||||
if (n.type === subflowType) {
|
if (n.type === subflowType) {
|
||||||
userCount++;
|
subflowUserCount++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('<tr class="node-info-subflow-row"><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>').appendTo(tableBody);
|
|
||||||
$('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+userCount+'</td></tr>').appendTo(tableBody);
|
|
||||||
}
|
}
|
||||||
|
if (node.type === "tab" || node.type === "subflow") {
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info."+(node.type==='tab'?'flow':'subflow'))+'</td><td></td></tr>').appendTo(tableBody);
|
||||||
|
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.tabName")+"</td><td></td></tr>").appendTo(tableBody);
|
||||||
|
$(propRow.children()[1]).text(node.label||node.name||"");
|
||||||
|
if (node.type === "tab") {
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
|
||||||
|
$(propRow.children()[1]).html((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
|
||||||
|
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
|
||||||
|
|
||||||
|
|
||||||
|
if (node.type !== "subflow" && node.name) {
|
||||||
|
$('<tr class="node-info-node-row"><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'">'+node.name+'</span></td></tr>').appendTo(tableBody);
|
||||||
|
}
|
||||||
|
if (!m) {
|
||||||
|
$('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.type")+"</td><td>"+node.type+"</td></tr>").appendTo(tableBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m && node.type != "subflow" && node.type != "comment") {
|
||||||
|
if (node._def) {
|
||||||
|
var count = 0;
|
||||||
|
var defaults = node._def.defaults;
|
||||||
|
for (var n in defaults) {
|
||||||
|
if (n != "name" && defaults.hasOwnProperty(n)) {
|
||||||
|
var val = node[n];
|
||||||
|
var type = typeof val;
|
||||||
|
count++;
|
||||||
|
propRow = $('<tr class="node-info-property-row'+(expandedSections.property?"":" hide")+'"><td>'+n+"</td><td></td></tr>").appendTo(tableBody);
|
||||||
|
if (defaults[n].type) {
|
||||||
|
var configNode = RED.nodes.node(val);
|
||||||
|
if (!configNode) {
|
||||||
|
RED.utils.createObjectElement(undefined).appendTo(propRow.children()[1]);
|
||||||
|
} else {
|
||||||
|
var configLabel = RED.utils.getNodeLabel(configNode,val);
|
||||||
|
var container = propRow.children()[1];
|
||||||
|
|
||||||
|
var div = $('<span>',{class:""}).appendTo(container);
|
||||||
|
var nodeDiv = $('<div>',{class:"palette_node palette_node_small"}).appendTo(div);
|
||||||
|
var colour = configNode._def.color;
|
||||||
|
var icon_url = RED.utils.getNodeIcon(configNode._def);
|
||||||
|
nodeDiv.css({'backgroundColor':colour, "cursor":"pointer"});
|
||||||
|
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
|
||||||
|
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
|
||||||
|
var nodeContainer = $('<span></span>').css({"verticalAlign":"top","marginLeft":"6px"}).html(configLabel).appendTo(container);
|
||||||
|
|
||||||
|
nodeDiv.on('dblclick',function() {
|
||||||
|
RED.editor.editConfig("", configNode.type, configNode.id);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RED.utils.createObjectElement(val).appendTo(propRow.children()[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
$('<tr class="node-info-property-expand blank"><td colspan="2"><a href="#" class=" node-info-property-header'+(expandedSections.property?" expanded":"")+'"><span class="node-info-property-show-more">'+RED._("sidebar.info.showMore")+'</span><span class="node-info-property-show-less">'+RED._("sidebar.info.showLess")+'</span> <i class="fa fa-caret-down"></i></a></td></tr>').appendTo(tableBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.type !== 'tab') {
|
||||||
|
if (m) {
|
||||||
|
$('<tr class="blank"><th colspan="2">'+RED._("sidebar.info.subflow")+'</th></tr>').appendTo(tableBody);
|
||||||
|
$('<tr class="node-info-subflow-row"><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>').appendTo(tableBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m) {
|
||||||
|
$('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+subflowUserCount+'</td></tr>').appendTo(tableBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoText = "";
|
||||||
|
if (!subflowNode && node.type !== "comment" && node.type !== "tab") {
|
||||||
|
infoSection.title.html(RED._("sidebar.info.nodeHelp"));
|
||||||
|
var helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
|
||||||
|
infoText = helpText;
|
||||||
|
} else if (node.type === "tab") {
|
||||||
|
infoSection.title.html(RED._("sidebar.info.flowDesc"));
|
||||||
|
infoText = marked(node.info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subflowNode) {
|
||||||
|
infoText = infoText + (marked(subflowNode.info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>'));
|
||||||
|
infoSection.title.html(RED._("sidebar.info.subflowDesc"));
|
||||||
|
} else if (node._def && node._def.info) {
|
||||||
|
infoSection.title.html(RED._("sidebar.info.nodeHelp"));
|
||||||
|
var info = node._def.info;
|
||||||
|
var textInfo = (typeof info === "function" ? info.call(node) : info);
|
||||||
|
// TODO: help
|
||||||
|
infoText = infoText + marked(textInfo);
|
||||||
|
}
|
||||||
|
if (infoText) {
|
||||||
|
setInfoText(infoText);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$(".node-info-property-header").click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
expandedSections["property"] = !expandedSections["property"];
|
||||||
|
$(this).toggleClass("expanded",expandedSections["property"]);
|
||||||
|
$(".node-info-property-row").toggle(expandedSections["property"]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
$(table).appendTo(nodeSection.content);
|
|
||||||
|
|
||||||
var infoText = "";
|
|
||||||
|
|
||||||
if (!subflowNode && node.type !== "comment" && node.type !== "tab") {
|
|
||||||
var helpText = $("script[data-help-name='"+node.type+"']").html()||"";
|
|
||||||
infoText = helpText;
|
|
||||||
} else if (node.type === "tab") {
|
|
||||||
infoText = marked(node.info||"");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subflowNode) {
|
|
||||||
infoText = infoText + marked(subflowNode.info||"");
|
|
||||||
} else if (node._def && node._def.info) {
|
|
||||||
var info = node._def.info;
|
|
||||||
var textInfo = (typeof info === "function" ? info.call(node) : info);
|
|
||||||
// TODO: help
|
|
||||||
infoText = infoText + marked(textInfo);
|
|
||||||
}
|
|
||||||
if (infoText) {
|
|
||||||
setInfoText(infoText);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$(".node-info-property-header").click(function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
expandedSections["property"] = !expandedSections["property"];
|
|
||||||
$(this).toggleClass("expanded",expandedSections["property"]);
|
|
||||||
$(".node-info-property-row").toggle(expandedSections["property"]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function setInfoText(infoText) {
|
function setInfoText(infoText) {
|
||||||
var info = addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(infoSection.content);
|
var info = addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(infoSection.content);
|
||||||
@ -342,22 +365,25 @@ RED.sidebar.info = (function() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
sections.hide();
|
// sections.hide();
|
||||||
//
|
refresh(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(html) {
|
function set(html,title) {
|
||||||
// tips.stop();
|
// tips.stop();
|
||||||
sections.show();
|
// sections.show();
|
||||||
nodeSection.container.hide();
|
// nodeSection.container.hide();
|
||||||
|
infoSection.title.text(title||"");
|
||||||
|
refresh(null);
|
||||||
$(infoSection.content).empty();
|
$(infoSection.content).empty();
|
||||||
setInfoText(html);
|
setInfoText(html);
|
||||||
$(".sidebar-node-info-stack").scrollTop(0);
|
$(".sidebar-node-info-stack").scrollTop(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshSelection(selection) {
|
||||||
|
if (selection === undefined) {
|
||||||
RED.events.on("view:selection-changed",function(selection) {
|
selection = RED.view.selection();
|
||||||
|
}
|
||||||
if (selection.nodes) {
|
if (selection.nodes) {
|
||||||
if (selection.nodes.length == 1) {
|
if (selection.nodes.length == 1) {
|
||||||
var node = selection.nodes[0];
|
var node = selection.nodes[0];
|
||||||
@ -366,6 +392,8 @@ RED.sidebar.info = (function() {
|
|||||||
} else {
|
} else {
|
||||||
refresh(node);
|
refresh(node);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
refresh(selection.nodes);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var activeWS = RED.workspaces.active();
|
var activeWS = RED.workspaces.active();
|
||||||
@ -378,11 +406,15 @@ RED.sidebar.info = (function() {
|
|||||||
if (workspace && workspace.info) {
|
if (workspace && workspace.info) {
|
||||||
refresh(workspace);
|
refresh(workspace);
|
||||||
} else {
|
} else {
|
||||||
clear();
|
refresh(null)
|
||||||
|
// clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RED.events.on("view:selection-changed",refreshSelection);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
|
@ -32,7 +32,11 @@ RED.tray = (function() {
|
|||||||
// var growButton = $('<a class="editor-tray-resize-button" style="cursor: w-resize;"><i class="fa fa-angle-left"></i></a>').appendTo(resizer);
|
// var growButton = $('<a class="editor-tray-resize-button" style="cursor: w-resize;"><i class="fa fa-angle-left"></i></a>').appendTo(resizer);
|
||||||
// var shrinkButton = $('<a class="editor-tray-resize-button" style="cursor: e-resize;"><i style="margin-left: 1px;" class="fa fa-angle-right"></i></a>').appendTo(resizer);
|
// var shrinkButton = $('<a class="editor-tray-resize-button" style="cursor: e-resize;"><i style="margin-left: 1px;" class="fa fa-angle-right"></i></a>').appendTo(resizer);
|
||||||
if (options.title) {
|
if (options.title) {
|
||||||
$('<div class="editor-tray-titlebar">'+options.title+'</div>').appendTo(header);
|
var titles = stack.map(function(e) { return e.options.title });
|
||||||
|
titles.push(options.title);
|
||||||
|
var title = '<ul class="editor-tray-breadcrumbs"><li>'+titles.join("</li><li>")+'</li></ul>';
|
||||||
|
|
||||||
|
$('<div class="editor-tray-titlebar">'+title+'</div>').appendTo(header);
|
||||||
}
|
}
|
||||||
if (options.width === Infinity) {
|
if (options.width === Infinity) {
|
||||||
options.maximized = true;
|
options.maximized = true;
|
||||||
@ -115,10 +119,10 @@ RED.tray = (function() {
|
|||||||
$("#editor-shade").show();
|
$("#editor-shade").show();
|
||||||
$("#palette-shade").show();
|
$("#palette-shade").show();
|
||||||
$(".sidebar-shade").show();
|
$(".sidebar-shade").show();
|
||||||
|
|
||||||
tray.preferredWidth = Math.max(el.width(),500);
|
tray.preferredWidth = Math.max(el.width(),500);
|
||||||
body.css({"minWidth":tray.preferredWidth-40});
|
if (!options.maximized) {
|
||||||
|
body.css({"minWidth":tray.preferredWidth-40});
|
||||||
|
}
|
||||||
if (options.width) {
|
if (options.width) {
|
||||||
if (options.width > $("#editor-stack").position().left-8) {
|
if (options.width > $("#editor-stack").position().left-8) {
|
||||||
options.width = $("#editor-stack").position().left-8;
|
options.width = $("#editor-stack").position().left-8;
|
||||||
@ -210,7 +214,7 @@ RED.tray = (function() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
show: function show(options) {
|
show: function show(options) {
|
||||||
if (stack.length > 0) {
|
if (stack.length > 0 && !options.overlay) {
|
||||||
var oldTray = stack[stack.length-1];
|
var oldTray = stack[stack.length-1];
|
||||||
oldTray.tray.css({
|
oldTray.tray.css({
|
||||||
right: -(oldTray.tray.width()+10)+"px"
|
right: -(oldTray.tray.width()+10)+"px"
|
||||||
@ -238,14 +242,21 @@ RED.tray = (function() {
|
|||||||
tray.tray.remove();
|
tray.tray.remove();
|
||||||
if (stack.length > 0) {
|
if (stack.length > 0) {
|
||||||
var oldTray = stack[stack.length-1];
|
var oldTray = stack[stack.length-1];
|
||||||
oldTray.tray.appendTo("#editor-stack");
|
if (!oldTray.options.overlay) {
|
||||||
setTimeout(function() {
|
oldTray.tray.appendTo("#editor-stack");
|
||||||
|
setTimeout(function() {
|
||||||
|
handleWindowResize();
|
||||||
|
oldTray.tray.css({right:0});
|
||||||
|
if (oldTray.options.show) {
|
||||||
|
oldTray.options.show();
|
||||||
|
}
|
||||||
|
},0);
|
||||||
|
} else {
|
||||||
handleWindowResize();
|
handleWindowResize();
|
||||||
oldTray.tray.css({right:0});
|
|
||||||
if (oldTray.options.show) {
|
if (oldTray.options.show) {
|
||||||
oldTray.options.show();
|
oldTray.options.show();
|
||||||
}
|
}
|
||||||
},0);
|
}
|
||||||
}
|
}
|
||||||
if (done) {
|
if (done) {
|
||||||
done();
|
done();
|
||||||
|
@ -29,6 +29,10 @@ RED.userSettings = (function() {
|
|||||||
if (settingsVisible) {
|
if (settingsVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!RED.user.hasPermission("settings.write")) {
|
||||||
|
RED.notify(RED._("user.errors.settings"),"error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
settingsVisible = true;
|
settingsVisible = true;
|
||||||
var tabContainer;
|
var tabContainer;
|
||||||
|
|
||||||
@ -127,10 +131,13 @@ RED.userSettings = (function() {
|
|||||||
|
|
||||||
var pane = $('<div id="user-settings-tab-view" class="node-help"></div>');
|
var pane = $('<div id="user-settings-tab-view" class="node-help"></div>');
|
||||||
|
|
||||||
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
currentEditorSettings.view = currentEditorSettings.view || {};
|
||||||
|
|
||||||
viewSettings.forEach(function(section) {
|
viewSettings.forEach(function(section) {
|
||||||
$('<h3></h3>').text(RED._(section.title)).appendTo(pane);
|
$('<h3></h3>').text(RED._(section.title)).appendTo(pane);
|
||||||
section.options.forEach(function(opt) {
|
section.options.forEach(function(opt) {
|
||||||
var initialState = RED.settings.get(opt.setting);
|
var initialState = currentEditorSettings.view[opt.setting];
|
||||||
var row = $('<div class="user-settings-row"></div>').appendTo(pane);
|
var row = $('<div class="user-settings-row"></div>').appendTo(pane);
|
||||||
var input;
|
var input;
|
||||||
if (opt.toggle) {
|
if (opt.toggle) {
|
||||||
@ -147,7 +154,10 @@ RED.userSettings = (function() {
|
|||||||
|
|
||||||
function setSelected(id, value) {
|
function setSelected(id, value) {
|
||||||
var opt = allSettings[id];
|
var opt = allSettings[id];
|
||||||
RED.settings.set(opt.setting,value);
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
currentEditorSettings.view = currentEditorSettings.view || {};
|
||||||
|
currentEditorSettings.view[opt.setting] = value;
|
||||||
|
RED.settings.set('editor', currentEditorSettings);
|
||||||
var callback = opt.onchange;
|
var callback = opt.onchange;
|
||||||
if (typeof callback === 'string') {
|
if (typeof callback === 'string') {
|
||||||
callback = RED.actions.get(callback);
|
callback = RED.actions.get(callback);
|
||||||
@ -158,8 +168,9 @@ RED.userSettings = (function() {
|
|||||||
}
|
}
|
||||||
function toggle(id) {
|
function toggle(id) {
|
||||||
var opt = allSettings[id];
|
var opt = allSettings[id];
|
||||||
var state = RED.settings.get(opt.setting);
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
setSelected(id,!state);
|
currentEditorSettings.view = currentEditorSettings.view || {};
|
||||||
|
setSelected(id,!currentEditorSettings.view[opt.setting]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -185,21 +196,26 @@ RED.userSettings = (function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||||
|
currentEditorSettings.view = currentEditorSettings.view || {};
|
||||||
|
var editorSettingsChanged = false;
|
||||||
viewSettings.forEach(function(section) {
|
viewSettings.forEach(function(section) {
|
||||||
section.options.forEach(function(opt) {
|
section.options.forEach(function(opt) {
|
||||||
if (opt.oldSetting) {
|
if (opt.oldSetting) {
|
||||||
var oldValue = RED.settings.get(opt.oldSetting);
|
var oldValue = RED.settings.get(opt.oldSetting);
|
||||||
if (oldValue !== undefined && oldValue !== null) {
|
if (oldValue !== undefined && oldValue !== null) {
|
||||||
RED.settings.set(opt.setting,oldValue);
|
currentEditorSettings.view[opt.setting] = oldValue;
|
||||||
|
editorSettingsChanged = true;
|
||||||
RED.settings.remove(opt.oldSetting);
|
RED.settings.remove(opt.oldSetting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allSettings[opt.setting] = opt;
|
allSettings[opt.setting] = opt;
|
||||||
if (opt.onchange) {
|
if (opt.onchange) {
|
||||||
var value = RED.settings.get(opt.setting);
|
var value = currentEditorSettings.view[opt.setting];
|
||||||
if (value === null && opt.hasOwnProperty('default')) {
|
if ((value === null || value === undefined) && opt.hasOwnProperty('default')) {
|
||||||
value = opt.default;
|
value = opt.default;
|
||||||
RED.settings.set(opt.setting,value);
|
currentEditorSettings.view[opt.setting] = value;
|
||||||
|
editorSettingsChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var callback = opt.onchange;
|
var callback = opt.onchange;
|
||||||
@ -212,6 +228,9 @@ RED.userSettings = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (editorSettingsChanged) {
|
||||||
|
RED.settings.set('editor',currentEditorSettings);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -765,6 +765,14 @@ RED.utils = (function() {
|
|||||||
return RED.text.bidi.enforceTextDirectionWithUCC(l);
|
return RED.text.bidi.enforceTextDirectionWithUCC(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addSpinnerOverlay(container,contain) {
|
||||||
|
var spinner = $('<div class="projects-dialog-spinner "><img src="red/images/spin.svg"/></div>').appendTo(container);
|
||||||
|
if (contain) {
|
||||||
|
spinner.addClass('projects-dialog-spinner-contain');
|
||||||
|
}
|
||||||
|
return spinner;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createObjectElement: buildMessageElement,
|
createObjectElement: buildMessageElement,
|
||||||
getMessageProperty: getMessageProperty,
|
getMessageProperty: getMessageProperty,
|
||||||
@ -774,5 +782,6 @@ RED.utils = (function() {
|
|||||||
getDefaultNodeIcon: getDefaultNodeIcon,
|
getDefaultNodeIcon: getDefaultNodeIcon,
|
||||||
getNodeIcon: getNodeIcon,
|
getNodeIcon: getNodeIcon,
|
||||||
getNodeLabel: getNodeLabel,
|
getNodeLabel: getNodeLabel,
|
||||||
|
addSpinnerOverlay: addSpinnerOverlay
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -399,10 +399,10 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#chart").focus(function() {
|
$("#chart").focus(function() {
|
||||||
$("#workspace-tabs").addClass("workspace-focussed")
|
$("#workspace-tabs").addClass("workspace-focussed");
|
||||||
});
|
});
|
||||||
$("#chart").blur(function() {
|
$("#chart").blur(function() {
|
||||||
$("#workspace-tabs").removeClass("workspace-focussed")
|
$("#workspace-tabs").removeClass("workspace-focussed");
|
||||||
});
|
});
|
||||||
|
|
||||||
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
|
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
|
||||||
@ -1147,7 +1147,6 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) {
|
var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) {
|
||||||
if (key === 'nodes') {
|
if (key === 'nodes') {
|
||||||
return value.map(function(n) { return n.id })
|
return value.map(function(n) { return n.id })
|
||||||
@ -2773,7 +2772,7 @@ RED.view = (function() {
|
|||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
return gridSize;
|
return gridSize;
|
||||||
} else {
|
} else {
|
||||||
gridSize = v;
|
gridSize = Math.max(5,v);
|
||||||
updateGrid();
|
updateGrid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,10 +244,16 @@ RED.workspaces = (function() {
|
|||||||
if (tab.disabled) {
|
if (tab.disabled) {
|
||||||
$("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('workspace-disabled');
|
$("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('workspace-disabled');
|
||||||
}
|
}
|
||||||
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
|
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() <= 1);
|
||||||
|
if (workspace_tabs.count() === 1) {
|
||||||
|
showWorkspace();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onremove: function(tab) {
|
onremove: function(tab) {
|
||||||
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
|
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() <= 1);
|
||||||
|
if (workspace_tabs.count() === 0) {
|
||||||
|
hideWorkspace();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onreorder: function(oldOrder, newOrder) {
|
onreorder: function(oldOrder, newOrder) {
|
||||||
RED.history.push({t:'reorder',order:oldOrder,dirty:RED.nodes.dirty()});
|
RED.history.push({t:'reorder',order:oldOrder,dirty:RED.nodes.dirty()});
|
||||||
@ -261,6 +267,16 @@ RED.workspaces = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function showWorkspace() {
|
||||||
|
$("#workspace .red-ui-tabs").show()
|
||||||
|
$("#chart").show()
|
||||||
|
$("#workspace-footer").children().show()
|
||||||
|
}
|
||||||
|
function hideWorkspace() {
|
||||||
|
$("#workspace .red-ui-tabs").hide()
|
||||||
|
$("#chart").hide()
|
||||||
|
$("#workspace-footer").children().hide()
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
createWorkspaceTabs();
|
createWorkspaceTabs();
|
||||||
@ -280,6 +296,8 @@ RED.workspaces = (function() {
|
|||||||
RED.actions.add("core:add-flow",addWorkspace);
|
RED.actions.add("core:add-flow",addWorkspace);
|
||||||
RED.actions.add("core:edit-flow",editWorkspace);
|
RED.actions.add("core:edit-flow",editWorkspace);
|
||||||
RED.actions.add("core:remove-flow",removeWorkspace);
|
RED.actions.add("core:remove-flow",removeWorkspace);
|
||||||
|
|
||||||
|
hideWorkspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
function editWorkspace(id) {
|
function editWorkspace(id) {
|
||||||
@ -294,6 +312,9 @@ RED.workspaces = (function() {
|
|||||||
workspace_tabs.removeTab(ws.id);
|
workspace_tabs.removeTab(ws.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ws.id === activeWorkspace) {
|
||||||
|
activeWorkspace = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWorkspaceOrder(order) {
|
function setWorkspaceOrder(order) {
|
||||||
|
@ -188,6 +188,7 @@ RED.user = (function() {
|
|||||||
RED.settings.load(function() {
|
RED.settings.load(function() {
|
||||||
RED.notify(RED._("user.loggedInAs",{name:RED.settings.user.username}),"success");
|
RED.notify(RED._("user.loggedInAs",{name:RED.settings.user.username}),"success");
|
||||||
updateUserMenu();
|
updateUserMenu();
|
||||||
|
RED.events.emit("login",RED.settings.user.username);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -230,10 +231,66 @@ RED.user = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var readRE = /^((.+)\.)?read$/
|
||||||
|
var writeRE = /^((.+)\.)?write$/
|
||||||
|
|
||||||
|
function hasPermission(permission) {
|
||||||
|
if (permission === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!RED.settings.user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return checkPermission(RED.settings.user.permissions||"",permission);
|
||||||
|
}
|
||||||
|
function checkPermission(userScope,permission) {
|
||||||
|
if (permission === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (Array.isArray(permission)) {
|
||||||
|
// Multiple permissions requested - check each one
|
||||||
|
for (i=0;i<permission.length;i++) {
|
||||||
|
if (!checkPermission(userScope,permission[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All permissions check out
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(userScope)) {
|
||||||
|
if (userScope.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i=0;i<userScope.length;i++) {
|
||||||
|
if (checkPermission(userScope[i],permission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userScope === "*" || userScope === permission) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userScope === "read" || userScope === "*.read") {
|
||||||
|
return readRE.test(permission);
|
||||||
|
} else if (userScope === "write" || userScope === "*.write") {
|
||||||
|
return writeRE.test(permission);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
login: login,
|
login: login,
|
||||||
logout: logout
|
logout: logout,
|
||||||
|
hasPermission: hasPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -66,4 +66,4 @@ $editor-button-background-primary-hover: #6E0A1E;
|
|||||||
$editor-button-color: #999;
|
$editor-button-color: #999;
|
||||||
$editor-button-background: #fff;
|
$editor-button-background: #fff;
|
||||||
|
|
||||||
$shade-color: rgba(200,200,200,0.5);
|
$shade-color: rgba(160,160,160,0.5);
|
||||||
|
@ -15,15 +15,15 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
|
|
||||||
#node-dialog-view-diff {
|
.node-dialog-view-diff-panel {
|
||||||
.red-ui-editableList-container {
|
.red-ui-editableList-container {
|
||||||
border-radius:1px;
|
border-radius:1px;
|
||||||
padding:0;
|
padding:0;
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
}
|
}
|
||||||
#node-dialog-view-diff-diff {
|
.node-dialog-view-diff-diff {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top:80px;
|
top:30px;
|
||||||
bottom:10px;
|
bottom:10px;
|
||||||
left:10px;
|
left:10px;
|
||||||
right:10px;
|
right:10px;
|
||||||
@ -38,17 +38,31 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
// padding-bottom: 5px;
|
// padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
&.node-dialog-view-diff-panel-merge {
|
||||||
|
.node-dialog-view-diff-diff {
|
||||||
|
top: 80px
|
||||||
|
}
|
||||||
|
.node-dialog-view-diff-headers {
|
||||||
|
top: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#node-dialog-view-diff-headers {
|
|
||||||
|
.node-dialog-view-diff-headers {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left:237px;
|
left:237px;
|
||||||
right:18px;
|
right:18px;
|
||||||
top: 55px;
|
top: 5px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
div {
|
div {
|
||||||
height: 25px;
|
height: 25px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
padding-top: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -357,6 +371,7 @@
|
|||||||
.node-diff-added { color: #009900}
|
.node-diff-added { color: #009900}
|
||||||
.node-diff-deleted { color: #f80000}
|
.node-diff-deleted { color: #f80000}
|
||||||
.node-diff-changed { color: #f89406}
|
.node-diff-changed { color: #f89406}
|
||||||
|
.node-diff-unchanged { color: #bbb}
|
||||||
.node-diff-conflicted { color: purple}
|
.node-diff-conflicted { color: purple}
|
||||||
|
|
||||||
|
|
||||||
@ -500,37 +515,169 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#node-dialog-confirm-deploy {
|
ul.node-dialog-configm-deploy-list {
|
||||||
.node-dialog-confirm-row {
|
font-size: 0.9em;
|
||||||
text-align: left; padding-top: 10px;
|
width: 400px;
|
||||||
|
margin: 10px auto;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-dialog-confirm-conflict-row {
|
||||||
|
img {
|
||||||
|
vertical-align:middle;
|
||||||
|
height: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
ul {
|
i {
|
||||||
font-size: 0.9em;
|
vertical-align:middle;
|
||||||
width: 400px;
|
text-align: center;
|
||||||
margin: 10px auto;
|
font-size: 30px;
|
||||||
text-align: left;
|
width: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
.node-dialog-confirm-conflict-row {
|
div {
|
||||||
img {
|
vertical-align: middle;
|
||||||
vertical-align:middle;
|
width: calc(100% - 60px);
|
||||||
height: 30px;
|
display:inline-block;
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
i {
|
|
||||||
vertical-align:middle;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 30px;
|
|
||||||
width: 30px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
vertical-align: middle;
|
|
||||||
width: calc(100% - 60px);
|
|
||||||
display:inline-block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#node-diff-toolbar-resolved-conflicts .node-diff-status {
|
#node-diff-toolbar-resolved-conflicts .node-diff-status {
|
||||||
margin:0;
|
margin:0;
|
||||||
}
|
}
|
||||||
|
.node-diff-text-diff-button {
|
||||||
|
float: right;
|
||||||
|
margin: 2px 3px;
|
||||||
|
line-height: 14px;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.node-text-diff {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y:auto;
|
||||||
|
table {
|
||||||
|
margin: 10px;
|
||||||
|
border: 1px solid $secondary-border-color;
|
||||||
|
border-radius: 3px;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
td.lineno {
|
||||||
|
font-family: monospace;
|
||||||
|
text-align: right;
|
||||||
|
color: #aaa;
|
||||||
|
background: #f6f6f6;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
td.lineno:nth-child(3) {
|
||||||
|
border-left: 1px solid $secondary-border-color;
|
||||||
|
}
|
||||||
|
td.linetext {
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
padding: 1px 5px;
|
||||||
|
span.prefix {
|
||||||
|
width: 30px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td.blank {
|
||||||
|
background: #f6f6f6;
|
||||||
|
}
|
||||||
|
td.added {
|
||||||
|
background: #eefaee;
|
||||||
|
}
|
||||||
|
td.removed {
|
||||||
|
background: #fadddd;
|
||||||
|
}
|
||||||
|
tr.mergeHeader td {
|
||||||
|
color: #800080;
|
||||||
|
background: #e5f9ff;
|
||||||
|
height: 26px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
tr.mergeHeader-separator td {
|
||||||
|
color: #800080;
|
||||||
|
background: darken(#e5f9ff, 10%);
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
tr.mergeHeader-ours td {
|
||||||
|
border-top: 2px solid darken(#e5f9ff, 10%);
|
||||||
|
}
|
||||||
|
tr.mergeHeader-theirs td {
|
||||||
|
border-bottom: 2px solid darken(#e5f9ff, 10%);
|
||||||
|
}
|
||||||
|
td.unchanged {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
tr.unchanged {
|
||||||
|
background: #fefefe;
|
||||||
|
}
|
||||||
|
tr.start-block {
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
tr.end-block {
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
tr.node-text-diff-file-header td {
|
||||||
|
.filename {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
background: #f3f3f3;
|
||||||
|
padding: 5px 10px 5px 0;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
i.node-diff-chevron {
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr.node-text-diff-file-header.collapsed {
|
||||||
|
td i.node-diff-chevron {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr.node-text-diff-commit-header td {
|
||||||
|
background: #f3f3f3;
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: #333;
|
||||||
|
h3 {
|
||||||
|
font-size: 1.4em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.commit-summary {
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
padding-top: 5px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.commit-body {
|
||||||
|
margin-bottom:15px;
|
||||||
|
white-space: pre;
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.node-text-diff-header td {
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: left;
|
||||||
|
color: #666;
|
||||||
|
background: #ffd;
|
||||||
|
height: 30px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
tr.node-text-diff-expand td {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #ffc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -158,7 +158,10 @@
|
|||||||
top: -1px;
|
top: -1px;
|
||||||
bottom: -1px;
|
bottom: -1px;
|
||||||
}
|
}
|
||||||
|
#full-shade {
|
||||||
|
@include shade;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-form,#dialog-form, #node-config-dialog-edit-form {
|
.dialog-form,#dialog-form, #node-config-dialog-edit-form {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -196,6 +199,10 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-warning {
|
||||||
|
border-color: #d6615f;
|
||||||
|
}
|
||||||
|
|
||||||
.node-text-editor {
|
.node-text-editor {
|
||||||
border:1px solid #ccc;
|
border:1px solid #ccc;
|
||||||
border-radius:5px;
|
border-radius:5px;
|
||||||
@ -209,8 +216,10 @@
|
|||||||
height: 34px;
|
height: 34px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-radius: 4px;
|
border-radius: 2px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
&.toggle {
|
&.toggle {
|
||||||
@include workspace-button-toggle;
|
@include workspace-button-toggle;
|
||||||
}
|
}
|
||||||
@ -219,6 +228,7 @@
|
|||||||
|
|
||||||
.editor-button-small {
|
.editor-button-small {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
min-width: 20px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
@ -501,6 +501,11 @@ textarea.span1,
|
|||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.disabled {
|
||||||
|
color: #bbb !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
input[disabled],
|
input[disabled],
|
||||||
select[disabled],
|
select[disabled],
|
||||||
textarea[disabled],
|
textarea[disabled],
|
||||||
|
@ -79,6 +79,7 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 6px 14px;
|
padding: 6px 14px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
color: $editor-button-color;
|
color: $editor-button-color;
|
||||||
background: $editor-button-background;
|
background: $editor-button-background;
|
||||||
|
|
||||||
@ -142,3 +143,10 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ui-widget-overlay {
|
||||||
|
@include shade;
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
@ -48,29 +48,30 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|
||||||
&.disabled {
|
&.disabled, &:disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: $workspace-button-color-disabled !important;
|
color: $workspace-button-color-disabled !important;
|
||||||
}
|
}
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
&:not(.disabled):hover {
|
&:not(.disabled):not(:disabled):hover, {
|
||||||
color: $workspace-button-color-hover !important;
|
color: $workspace-button-color-hover !important;
|
||||||
background: $workspace-button-background-hover;
|
background: $workspace-button-background-hover;
|
||||||
}
|
}
|
||||||
&:not(.disabled):focus {
|
&:not(.disabled):not(:disabled):focus {
|
||||||
color: $workspace-button-color-focus !important;
|
color: $workspace-button-color-focus !important;
|
||||||
}
|
}
|
||||||
&:not(.disabled):active {
|
&:not(.disabled):not(:disabled):active {
|
||||||
color: $workspace-button-color-active !important;
|
color: $workspace-button-color-active !important;
|
||||||
background: $workspace-button-background-active;
|
background: $workspace-button-background-active;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
&.selected:not(.disabled) {
|
// &.selected:not(.disabled):not(:disabled) {
|
||||||
color: $workspace-button-color-selected !important;
|
// color: $workspace-button-color-selected !important;
|
||||||
background: $workspace-button-background-active;
|
// background: $workspace-button-background-active;
|
||||||
}
|
// background: #9f9;
|
||||||
|
// }
|
||||||
.button-group &:not(:first-child) {
|
.button-group &:not(:first-child) {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
@ -94,9 +95,30 @@
|
|||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-row &:not(:first-child) {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 1px solid $workspace-button-color-focus-outline;
|
outline: 1px solid $workspace-button-color-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
border-color: $editor-button-background-primary;
|
||||||
|
color: $editor-button-color-primary !important;
|
||||||
|
background: $editor-button-background-primary;
|
||||||
|
&.disabled, &.ui-state-disabled {
|
||||||
|
background: none;
|
||||||
|
color: $editor-button-color !important;
|
||||||
|
border-color: $form-input-border-color;
|
||||||
|
}
|
||||||
|
&:not(.disabled):not(.ui-button-disabled):hover {
|
||||||
|
border-color: $editor-button-background-primary-hover;
|
||||||
|
background: $editor-button-background-primary-hover;
|
||||||
|
color: $editor-button-color-primary !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.button-group-vertical {
|
.button-group-vertical {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -132,21 +154,21 @@
|
|||||||
color: $editor-button-color !important;
|
color: $editor-button-color !important;
|
||||||
background: $editor-button-background;
|
background: $editor-button-background;
|
||||||
|
|
||||||
&.primary {
|
// &.primary {
|
||||||
border-color: $editor-button-background-primary;
|
// border-color: $editor-button-background-primary;
|
||||||
color: $editor-button-color-primary !important;
|
// color: $editor-button-color-primary !important;
|
||||||
background: $editor-button-background-primary;
|
// background: $editor-button-background-primary;
|
||||||
&.disabled, &.ui-state-disabled {
|
// &.disabled, &.ui-state-disabled {
|
||||||
background: none;
|
// background: none;
|
||||||
color: $editor-button-color !important;
|
// color: $editor-button-color !important;
|
||||||
border-color: $form-input-border-color;
|
// border-color: $form-input-border-color;
|
||||||
}
|
// }
|
||||||
&:not(.disabled):not(.ui-button-disabled):hover {
|
// &:not(.disabled):not(.ui-button-disabled):hover {
|
||||||
border-color: $editor-button-background-primary-hover;
|
// border-color: $editor-button-background-primary-hover;
|
||||||
background: $editor-button-background-primary-hover;
|
// background: $editor-button-background-primary-hover;
|
||||||
color: $editor-button-color-primary !important;
|
// color: $editor-button-color-primary !important;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
&:not(.disabled):hover {
|
&:not(.disabled):hover {
|
||||||
//color: $editor-button-color;
|
//color: $editor-button-color;
|
||||||
}
|
}
|
||||||
@ -226,3 +248,6 @@
|
|||||||
background: $shade-color;
|
background: $shade-color;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
.component-shade {
|
||||||
|
@include shade
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
#notifications {
|
#notifications {
|
||||||
z-index: 10000;
|
z-index: 100;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
margin-left: -250px;
|
margin-left: -250px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@ -34,6 +34,10 @@
|
|||||||
border-left-width: 16px;
|
border-left-width: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.notification p:first-child {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
.notification a {
|
.notification a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -76,48 +76,9 @@
|
|||||||
border-bottom: 1px solid $primary-border-color;
|
border-bottom: 1px solid $primary-border-color;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.palette-module-button-group {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
a {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.palette-module-shade {
|
|
||||||
@include shade;
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
||||||
#palette-module-install-shade {
|
|
||||||
padding-top: 80px;
|
|
||||||
}
|
|
||||||
.palette-module-shade-status {
|
.palette-module-shade-status {
|
||||||
color: #666;
|
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 {
|
.palette-module-updated {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
@ -224,3 +185,48 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.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-section {
|
||||||
|
padding:0 !important;
|
||||||
|
background: #f9f9f9 !important;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
}
|
||||||
|
.palette-module-button-group {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
a {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.palette-module-shade {
|
||||||
|
@include shade;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
#palette-module-install-shade {
|
||||||
|
padding-top: 80px;
|
||||||
|
}
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
}
|
}
|
||||||
.palette-header i {
|
.palette-header > i {
|
||||||
margin: 3px 10px 3px 3px;
|
margin: 3px 10px 3px 3px;
|
||||||
-webkit-transition: all 0.2s ease-in-out;
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
-moz-transition: all 0.2s ease-in-out;
|
-moz-transition: all 0.2s ease-in-out;
|
||||||
|
875
editor/sass/projects.scss
Normal file
875
editor/sass/projects.scss
Normal file
@ -0,0 +1,875 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#projects-dialog {
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#project-settings-tab-settings {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-shade {
|
||||||
|
background: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-edit-form form {
|
||||||
|
margin: 0;
|
||||||
|
.form-row {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
label {
|
||||||
|
color: #555;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
&.projects-edit-form-inline-label {
|
||||||
|
font-weight: normal;
|
||||||
|
color: inherit;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type=text], input[type=password],textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type=checkbox], input[type=radio] {
|
||||||
|
width: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.projects-edit-form-sublabel {
|
||||||
|
color: #999;
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: -15px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.project-settings-tab-pane {
|
||||||
|
& * .projects-edit-form-sublabel {
|
||||||
|
margin-right: 50px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
bottom: 1px;
|
||||||
|
left: 1px;
|
||||||
|
right: 1px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
background: white;
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: -0.25em;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
&.projects-dialog-spinner-sidebar {
|
||||||
|
background: white;
|
||||||
|
padding:0;
|
||||||
|
img {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.projects-version-control-spinner-sidebar {
|
||||||
|
background: white;
|
||||||
|
padding:0;
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.projects-dialog-spinner-contain {
|
||||||
|
padding: 0;
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.projects-dialog-screen-start {
|
||||||
|
.projects-dialog-screen-start-hero {
|
||||||
|
// background: url(https://nodered.org/images/title-wave.png) no-repeat 0% 100% #8f0000;
|
||||||
|
// background-size: contain;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2em;
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 60px;
|
||||||
|
color: #555;
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #f0f0f0;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-screen-start-body {
|
||||||
|
min-height: 400px;
|
||||||
|
line-height: 1.6em;
|
||||||
|
p {
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
p:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button.editor-button {
|
||||||
|
width: calc(50% - 40px);
|
||||||
|
margin: 20px;
|
||||||
|
height: 175px;
|
||||||
|
line-height: 2em;
|
||||||
|
font-size: 1.5em !important;
|
||||||
|
i {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
&:hover i {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-group {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-screen-create {
|
||||||
|
min-height: 500px;
|
||||||
|
button.projects-dialog-screen-create-type {
|
||||||
|
height: auto;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.button-group {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-dialog-screen-secret {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-container {
|
||||||
|
border: 1px solid $secondary-border-color;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-inner-container {
|
||||||
|
height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
position:relative;
|
||||||
|
.red-ui-editableList-border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list {
|
||||||
|
li {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-entry {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-left: 3px solid #fff;
|
||||||
|
border-right: 3px solid #fff;
|
||||||
|
&.projects-list-entry-current {
|
||||||
|
&:not(.selectable) {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #f3f3f3;
|
||||||
|
// border-left-color: #aaa;
|
||||||
|
// border-right-color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-entry-icon {
|
||||||
|
i {
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 2em;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
background: #efefef;
|
||||||
|
border-left-color:#999;
|
||||||
|
border-right-color:#999;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-entry-icon {
|
||||||
|
margin: 0 10px 0 5px;
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-entry-name {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-entry-current {
|
||||||
|
float: right;
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #999;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
.projects-dialog-project-list-entry-tools {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 30px;
|
||||||
|
display: none;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.projects-dialog-project-list-entry-tools {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-screen-create-type.editor-button.toggle.selected:not(.disabled):not(:disabled) {
|
||||||
|
background: #fff !important;
|
||||||
|
color: #666 !important;
|
||||||
|
}
|
||||||
|
.projects-dialog-screen-input-status {
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 8px;
|
||||||
|
width: 70px;
|
||||||
|
height: 30px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-stack-info {
|
||||||
|
height: 100px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid $secondary-border-color;
|
||||||
|
color: #333;
|
||||||
|
i {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-stack {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.palette-category {
|
||||||
|
&:not(.palette-category-expanded) button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#project-settings-tab-deps {
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.red-ui-editableList-border {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.red-ui-editableList-item-content {
|
||||||
|
padding: 0px 6px;
|
||||||
|
}
|
||||||
|
.palette-module-header {
|
||||||
|
padding: 6px 4px;
|
||||||
|
}
|
||||||
|
.palette-module-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.palette-module-unused {
|
||||||
|
& > * {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
// border: 1px dashed #bbb;
|
||||||
|
}
|
||||||
|
.palette-module-unknown {
|
||||||
|
border: 1px dashed #aaa;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.palette-module-not-installed {
|
||||||
|
border: 1px dashed #b07575;
|
||||||
|
background: #fee;
|
||||||
|
i.fa-warning {
|
||||||
|
color: #b07575; //#b72828;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.project-settings-tab-pane {
|
||||||
|
position: absolute;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
bottom:0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 20px 20px;
|
||||||
|
}
|
||||||
|
.sidebar-version-control {
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
background: #f9f9f9;
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
padding:0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-editableList-border {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control-change-container {
|
||||||
|
position: relative;
|
||||||
|
height: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: height 0.2s ease-in-out;
|
||||||
|
&:first-child {
|
||||||
|
// border-bottom: 1px solid $primary-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-merging {
|
||||||
|
.sidebar-version-control-change-container {
|
||||||
|
height: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-slide-box {
|
||||||
|
position:absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
height:0;
|
||||||
|
transition: height 0.2s ease-in-out;
|
||||||
|
background: #f6f6f6;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
&.sidebar-version-control-slide-box-top {
|
||||||
|
z-index: 4;
|
||||||
|
top: 0px;
|
||||||
|
left: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 280px;
|
||||||
|
border-left: 1px solid $primary-border-color;
|
||||||
|
border-right: 1px solid $primary-border-color;
|
||||||
|
border-bottom: 1px solid $primary-border-color;
|
||||||
|
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
|
||||||
|
|
||||||
|
color: #666;
|
||||||
|
background: #f6f6f6;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
}
|
||||||
|
&.sidebar-version-control-slide-box-bottom {
|
||||||
|
bottom: 0px;
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 110px;
|
||||||
|
margin: 10px;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 1px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.projects-branch-list {
|
||||||
|
position: relative;
|
||||||
|
.red-ui-searchBox-container {
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
border-left: 1px solid $secondary-border-color;
|
||||||
|
border-right: 1px solid $secondary-border-color;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.red-ui-editableList {
|
||||||
|
border: 1px solid $secondary-border-color;
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
& > .red-ui-editableList-border {
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.uneditable-input .projects-branch-list {
|
||||||
|
.red-ui-editableList {
|
||||||
|
border-left: none;
|
||||||
|
border-bottom: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.red-ui-searchBox-container {
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-slide-box-header {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control-slide-box-toolbar {
|
||||||
|
padding: 0 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-branch-list-entry {
|
||||||
|
padding: 5px 8px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
&.selected {
|
||||||
|
border-left-color:#999;
|
||||||
|
border-right-color:#999;
|
||||||
|
}
|
||||||
|
border-left: 2px solid #fff;
|
||||||
|
border-right: 2px solid #fff;
|
||||||
|
margin: 0 1px;
|
||||||
|
i { width: 16px; text-align: center}
|
||||||
|
&.input-error {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
&:not(.input-error):hover {
|
||||||
|
background: #f3f3f3;
|
||||||
|
border-left-color:#999;
|
||||||
|
border-right-color:#999;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
span.current {
|
||||||
|
float: right;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control-change-entry {
|
||||||
|
height: 20px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
span {
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: currentColor;
|
||||||
|
&.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-change-entry-tools {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.sidebar-version-control-change-entry-tools {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.node-info-none {
|
||||||
|
text-align: center;
|
||||||
|
background: #fefefe;
|
||||||
|
white-space: normal;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control-commit-entry {
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-more {
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-sha {
|
||||||
|
float: right;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #c38888;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.85em;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-subject {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-refs {
|
||||||
|
min-height: 22px;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-ref {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.7em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-date {
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-user {
|
||||||
|
float: right;
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-commit-head {
|
||||||
|
}
|
||||||
|
.sidebar-version-control-change-header {
|
||||||
|
color: #666;
|
||||||
|
background: #f6f6f6;
|
||||||
|
padding: 4px 10px;
|
||||||
|
height: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
border-bottom: 1px solid $secondary-border-color;
|
||||||
|
i {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-repo-toolbar {
|
||||||
|
color: #666;
|
||||||
|
background: #f6f6f6;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control-repo-count {
|
||||||
|
margin-right: 8px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-repo-action {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.sidebar-version-control-repo-sub-action {
|
||||||
|
width: calc(50% - 5px);
|
||||||
|
margin-right: 5px;
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-file-listing-container > .red-ui-editableList > .red-ui-editableList-border {
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
|
||||||
|
}
|
||||||
|
.red-ui-editableList-container .projects-dialog-file-list {
|
||||||
|
.red-ui-editableList-border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-file-list-entry {
|
||||||
|
padding: 3px 0;
|
||||||
|
border-left: 2px solid #fff;
|
||||||
|
border-right: 2px solid #fff;
|
||||||
|
&.projects-list-entry-current {
|
||||||
|
&:not(.selectable) {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #f3f3f3;
|
||||||
|
border-left-color:#999;
|
||||||
|
border-right-color:#999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.unselectable {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #999;
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
background: #efefef;
|
||||||
|
border-left-color:#999;
|
||||||
|
border-right-color:#999;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
.projects-dialog-file-list-entry-folder {
|
||||||
|
margin: 0 10px 0 0px;
|
||||||
|
|
||||||
|
.fa-angle-right {
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-file-list-entry-file {
|
||||||
|
margin: 0 10px 0 20px;
|
||||||
|
}
|
||||||
|
.projects-dialog-file-list-entry-name {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
&.expanded .fa-angle-right {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-file-list-entry-file-type-git { color: #999 }
|
||||||
|
|
||||||
|
.projects-dialog-remote-list {
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-dialog-sshkey-list {
|
||||||
|
li {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
&.projects-dialog-sshkey-list-small {
|
||||||
|
.projects-dialog-sshkey-list-entry {
|
||||||
|
padding: 6px 0;
|
||||||
|
i {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-entry-name {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-entry-current {
|
||||||
|
margin-right: 10px;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
.projects-dialog-sshkey-list {
|
||||||
|
li:last-child {
|
||||||
|
border-bottom: 0px none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-entry {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-left: 3px solid #fff;
|
||||||
|
border-right: 3px solid #fff;
|
||||||
|
&.sshkey-list-entry-current {
|
||||||
|
&:not(.selectable) {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #f3f3f3;
|
||||||
|
border-left-color: #aaa;
|
||||||
|
border-right-color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 2em;
|
||||||
|
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
background: #efefef;
|
||||||
|
border-left-color:#999;
|
||||||
|
border-right-color:#999;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-entry-icon {
|
||||||
|
margin: 0 10px 0 5px;
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-entry-name {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-entry-current {
|
||||||
|
float: right;
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #999;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
.projects-dialog-sshkey-list-button-remove {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.projects-dialog-ssh-public-key {
|
||||||
|
position: relative;
|
||||||
|
padding: 15px 20px 0;
|
||||||
|
pre {
|
||||||
|
position: relative;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-dialog-ssh-key-list {
|
||||||
|
li {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.projects-dialog-ssh-key-header {
|
||||||
|
padding: 10px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #f3f3f3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-dialog-list {
|
||||||
|
position: relative;
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
padding: 1px;
|
||||||
|
background: #f6f6f6;
|
||||||
|
li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-dialog-list-entry {
|
||||||
|
&.red-ui-search-empty {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.entry-icon {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 30px;
|
||||||
|
vertical-align: top;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.entry-name {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
&.current .entry-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.entry-detail {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-remote-name {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.entry-tools {
|
||||||
|
float: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.projects-dialog-list-dialog {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid $secondary-border-color;
|
||||||
|
.projects-edit-form-sublabel {
|
||||||
|
margin-top: -8px !important;
|
||||||
|
display: block !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-dialog-list-dialog-header {
|
||||||
|
font-weight: bold;
|
||||||
|
background: #f3f3f3;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
@ -48,12 +48,15 @@
|
|||||||
|
|
||||||
@import "userSettings";
|
@import "userSettings";
|
||||||
|
|
||||||
|
@import "projects";
|
||||||
|
|
||||||
|
|
||||||
@import "ui/common/editableList";
|
@import "ui/common/editableList";
|
||||||
@import "ui/common/searchBox";
|
@import "ui/common/searchBox";
|
||||||
@import "ui/common/typedInput";
|
@import "ui/common/typedInput";
|
||||||
@import "ui/common/nodeList";
|
@import "ui/common/nodeList";
|
||||||
@import "ui/common/checkboxSet";
|
@import "ui/common/checkboxSet";
|
||||||
|
@import "ui/common/stack";
|
||||||
|
|
||||||
@import "dragdrop";
|
@import "dragdrop";
|
||||||
|
|
||||||
|
@ -87,17 +87,22 @@ table.node-info tr.blank {
|
|||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.node-info-none {
|
||||||
|
font-style: italic;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
table.node-info tr:not(.blank) td:first-child{
|
table.node-info tr:not(.blank) td:first-child{
|
||||||
color: #666;
|
color: #444;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
padding: 3px 3px 3px 6px;
|
padding: 3px 3px 3px 6px;
|
||||||
|
background:#f9f9f9;
|
||||||
border-right: 1px solid #ddd;
|
border-right: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
table.node-info tr:not(.blank) td:last-child{
|
table.node-info tr:not(.blank) td:last-child{
|
||||||
padding: 3px 3px 3px 6px;
|
padding: 3px 3px 3px 6px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
div.node-info {
|
div.node-info {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
26
editor/sass/ui/common/stack.scss
Normal file
26
editor/sass/ui/common/stack.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
.red-ui-stack {
|
||||||
|
background: white;
|
||||||
|
.palette-category {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,17 +38,26 @@
|
|||||||
label {
|
label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: 5px;
|
||||||
input {
|
input {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input {
|
input, div.uneditable-input {
|
||||||
margin-bottom: 0;
|
//margin-bottom: 0;
|
||||||
|
}
|
||||||
|
div.uneditable-input {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
input[type='number'] {
|
input[type='number'] {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
h4 {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-settings-tab-view {
|
#user-settings-tab-view {
|
||||||
@ -57,3 +66,18 @@
|
|||||||
.user-settings-row {
|
.user-settings-row {
|
||||||
padding: 5px 10px 2px;
|
padding: 5px 10px 2px;
|
||||||
}
|
}
|
||||||
|
.user-settings-section {
|
||||||
|
position: relative;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.uneditable-input, input, textarea {
|
||||||
|
width: calc(100% - 150px);
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
height: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -77,35 +77,11 @@
|
|||||||
<div id="sidebar-separator"></div>
|
<div id="sidebar-separator"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="full-shade" class="hide"></div>
|
||||||
|
|
||||||
<div id="notifications"></div>
|
<div id="notifications"></div>
|
||||||
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
|
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
|
||||||
|
|
||||||
<div id="node-dialog-confirm-deploy" class="hide">
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<div id="node-dialog-confirm-deploy-config" class="node-dialog-confirm-row" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm">
|
|
||||||
<ul id="node-dialog-confirm-deploy-invalid-list"></ul>
|
|
||||||
</div>
|
|
||||||
<div id="node-dialog-confirm-deploy-unknown" class="node-dialog-confirm-row" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
|
|
||||||
<ul id="node-dialog-confirm-deploy-unknown-list"></ul>
|
|
||||||
</div>
|
|
||||||
<div id="node-dialog-confirm-deploy-conflict" class="node-dialog-confirm-row">
|
|
||||||
<div style="margin-left: 40px; margin-bottom: 10px;">
|
|
||||||
<span data-i18n="deploy.confirm.conflict"></span>
|
|
||||||
</div>
|
|
||||||
<div id="node-dialog-confirm-deploy-conflict-checking" class="node-dialog-confirm-conflict-row">
|
|
||||||
<img src="red/images/spin.svg"/><div data-i18n="deploy.confirm.conflictChecking"></div>
|
|
||||||
</div>
|
|
||||||
<div id="node-dialog-confirm-deploy-conflict-auto-merge" class="node-dialog-confirm-conflict-row">
|
|
||||||
<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>
|
|
||||||
</div>
|
|
||||||
<div id="node-dialog-confirm-deploy-conflict-manual-merge" class="node-dialog-confirm-conflict-row">
|
|
||||||
<i style="color: #999;" class="fa fa-exclamation"></i><div data-i18n="deploy.confirm.conflictManualMerge"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="node-dialog-library-save-confirm" class="hide">
|
<div id="node-dialog-library-save-confirm" class="hide">
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
|
<div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
|
||||||
@ -209,6 +185,14 @@
|
|||||||
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>
|
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
<script type="text/x-red" data-template-name="_markdown">
|
||||||
|
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
|
||||||
|
<!--<button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button>-->
|
||||||
|
</div>
|
||||||
|
<div class="form-row node-text-editor-row">
|
||||||
|
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-markdown"></div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
<script type="text/x-red" data-template-name="_buffer">
|
<script type="text/x-red" data-template-name="_buffer">
|
||||||
<div id="node-input-buffer-panels">
|
<div id="node-input-buffer-panels">
|
||||||
<div id="node-input-buffer-panel-str" class="red-ui-panel">
|
<div id="node-input-buffer-panel-str" class="red-ui-panel">
|
||||||
@ -226,7 +210,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="vendor/vendor.js"></script>
|
<script src="vendor/vendor.js"></script>
|
||||||
<script src="vendor/jsonata/jsonata.min.js"></script>
|
<script src="vendor/jsonata/jsonata.min.js"></script>
|
||||||
<script src="vendor/ace/ace.js"></script>
|
<script src="vendor/ace/ace.js"></script>
|
||||||
|
@ -194,6 +194,18 @@
|
|||||||
};
|
};
|
||||||
RED.comms.subscribe("debug",this.handleDebugMessage);
|
RED.comms.subscribe("debug",this.handleDebugMessage);
|
||||||
|
|
||||||
|
this.clearMessageList = function() {
|
||||||
|
RED.debug.clearMessageList(true);
|
||||||
|
if (subWindow) {
|
||||||
|
try {
|
||||||
|
subWindow.postMessage({event:"projectChange"},"*");
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RED.events.on("project:change", this.clearMessageList);
|
||||||
|
|
||||||
$("#debug-tab-open").click(function(e) {
|
$("#debug-tab-open").click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
|
subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
|
||||||
|
@ -69,7 +69,7 @@ RED.debug = (function() {
|
|||||||
// var filterTypeRow = $('<div class="debug-filter-row"></div>').appendTo(filterDialog);
|
// var filterTypeRow = $('<div class="debug-filter-row"></div>').appendTo(filterDialog);
|
||||||
// $('<select><option>Show all debug nodes</option><option>Show selected debug nodes</option><option>Show current flow only</option></select>').appendTo(filterTypeRow);
|
// $('<select><option>Show all debug nodes</option><option>Show selected debug nodes</option><option>Show current flow only</option></select>').appendTo(filterTypeRow);
|
||||||
|
|
||||||
var debugNodeListRow = $('<div class="debug-filter-row hide"></div>').appendTo(filterDialog);
|
var debugNodeListRow = $('<div class="debug-filter-row hide" id="debug-filter-node-list-row"></div>').appendTo(filterDialog);
|
||||||
var flowCheckboxes = {};
|
var flowCheckboxes = {};
|
||||||
var debugNodeListHeader = $('<div><span data-i18n="node-red:debug.sidebar.debugNodes"></span><span></span></div>');
|
var debugNodeListHeader = $('<div><span data-i18n="node-red:debug.sidebar.debugNodes"></span><span></span></div>');
|
||||||
var headerCheckbox = $('<input type="checkbox">').appendTo(debugNodeListHeader.find("span")[1]).checkboxSet();
|
var headerCheckbox = $('<input type="checkbox">').appendTo(debugNodeListHeader.find("span")[1]).checkboxSet();
|
||||||
@ -219,9 +219,7 @@ RED.debug = (function() {
|
|||||||
|
|
||||||
toolbar.find("#debug-tab-clear").click(function(e) {
|
toolbar.find("#debug-tab-clear").click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(".debug-message").remove();
|
clearMessageList(false);
|
||||||
messageCount = 0;
|
|
||||||
config.clear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -526,9 +524,28 @@ RED.debug = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearMessageList(clearFilter) {
|
||||||
|
$(".debug-message").remove();
|
||||||
|
config.clear();
|
||||||
|
if (!!clearFilter) {
|
||||||
|
clearFilterSettings();
|
||||||
|
}
|
||||||
|
refreshDebugNodeList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilterSettings() {
|
||||||
|
filteredNodes = {};
|
||||||
|
filterType = 'filterAll';
|
||||||
|
$('.debug-tab-filter-option').removeClass('selected');
|
||||||
|
$('#debug-tab-filterAll').addClass('selected');
|
||||||
|
$('#debug-tab-filter span').text(RED._('node-red:debug.sidebar.filterAll'));
|
||||||
|
$('#debug-filter-node-list-row').slideUp();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
refreshMessageList:refreshMessageList,
|
refreshMessageList:refreshMessageList,
|
||||||
handleDebugMessage: handleDebugMessage
|
handleDebugMessage: handleDebugMessage,
|
||||||
|
clearMessageList: clearMessageList
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -24,6 +24,8 @@ $(function() {
|
|||||||
RED.debug.handleDebugMessage(evt.data.msg);
|
RED.debug.handleDebugMessage(evt.data.msg);
|
||||||
} else if (evt.data.event === "workspaceChange") {
|
} else if (evt.data.event === "workspaceChange") {
|
||||||
RED.debug.refreshMessageList(evt.data.activeWorkspace);
|
RED.debug.refreshMessageList(evt.data.activeWorkspace);
|
||||||
|
} else if (evt.data.event === "projectChange") {
|
||||||
|
RED.debug.clearMessageList(true);
|
||||||
}
|
}
|
||||||
},false);
|
},false);
|
||||||
})
|
})
|
||||||
|
@ -136,6 +136,8 @@ module.exports = function(RED) {
|
|||||||
tlsNode.addTLSOptions(this.options);
|
tlsNode.addTLSOptions(this.options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// console.log(this.brokerurl,this.options);
|
||||||
|
|
||||||
// If there's no rejectUnauthorized already, then this could be an
|
// If there's no rejectUnauthorized already, then this could be an
|
||||||
// old config where this option was provided on the broker node and
|
// old config where this option was provided on the broker node and
|
||||||
// not the tls node
|
// not the tls node
|
||||||
|
57
red/api/admin/index.js
Normal file
57
red/api/admin/index.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var express = require("express");
|
||||||
|
|
||||||
|
var nodes = require("./nodes");
|
||||||
|
var flows = require("./flows");
|
||||||
|
var flow = require("./flow");
|
||||||
|
var auth = require("../auth");
|
||||||
|
|
||||||
|
var apiUtil = require("../util");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(runtime) {
|
||||||
|
flows.init(runtime);
|
||||||
|
flow.init(runtime);
|
||||||
|
nodes.init(runtime);
|
||||||
|
|
||||||
|
var needsPermission = auth.needsPermission;
|
||||||
|
|
||||||
|
var adminApp = express();
|
||||||
|
|
||||||
|
// Flows
|
||||||
|
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
||||||
|
adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// Flow
|
||||||
|
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,apiUtil.errorHandler);
|
||||||
|
adminApp.post("/flow",needsPermission("flows.write"),flow.post,apiUtil.errorHandler);
|
||||||
|
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete,apiUtil.errorHandler);
|
||||||
|
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||||
|
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||||
|
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||||
|
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler);
|
||||||
|
adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler);
|
||||||
|
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
||||||
|
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
return adminApp;
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var when = require("when");
|
var when = require("when");
|
||||||
var locales = require("./locales");
|
var apiUtils = require("../util");
|
||||||
var redNodes;
|
var redNodes;
|
||||||
var log;
|
var log;
|
||||||
var i18n;
|
var i18n;
|
||||||
@ -35,7 +35,7 @@ module.exports = {
|
|||||||
log.audit({event: "nodes.list.get"},req);
|
log.audit({event: "nodes.list.get"},req);
|
||||||
res.json(redNodes.getNodeList());
|
res.json(redNodes.getNodeList());
|
||||||
} else {
|
} else {
|
||||||
var lang = locales.determineLangFromHeaders(req.acceptsLanguages());
|
var lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||||
log.audit({event: "nodes.configs.get"},req);
|
log.audit({event: "nodes.configs.get"},req);
|
||||||
res.send(redNodes.getNodeConfigs(lang));
|
res.send(redNodes.getNodeConfigs(lang));
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ module.exports = {
|
|||||||
res.status(404).end();
|
res.status(404).end();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var lang = locales.determineLangFromHeaders(req.acceptsLanguages());
|
var lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||||
result = redNodes.getNodeConfig(id,lang);
|
result = redNodes.getNodeConfig(id,lang);
|
||||||
if (result) {
|
if (result) {
|
||||||
log.audit({event: "nodes.config.get",id:id},req);
|
log.audit({event: "nodes.config.get",id:id},req);
|
@ -22,7 +22,7 @@ var Tokens = require("./tokens");
|
|||||||
var Users = require("./users");
|
var Users = require("./users");
|
||||||
var permissions = require("./permissions");
|
var permissions = require("./permissions");
|
||||||
|
|
||||||
var theme = require("../theme");
|
var theme = require("../editor/theme");
|
||||||
|
|
||||||
var settings = null;
|
var settings = null;
|
||||||
var log = null
|
var log = null
|
||||||
|
@ -33,6 +33,7 @@ function handleStatus(event) {
|
|||||||
publish("status/"+event.id,event.status,true);
|
publish("status/"+event.id,event.status,true);
|
||||||
}
|
}
|
||||||
function handleRuntimeEvent(event) {
|
function handleRuntimeEvent(event) {
|
||||||
|
log.trace("runtime event: "+JSON.stringify(event));
|
||||||
publish("notification/"+event.id,event.payload||{},event.retain);
|
publish("notification/"+event.id,event.payload||{},event.retain);
|
||||||
}
|
}
|
||||||
function init(_server,runtime) {
|
function init(_server,runtime) {
|
||||||
@ -48,9 +49,9 @@ function init(_server,runtime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
var Tokens = require("./auth/tokens");
|
var Tokens = require("../auth/tokens");
|
||||||
var Users = require("./auth/users");
|
var Users = require("../auth/users");
|
||||||
var Permissions = require("./auth/permissions");
|
var Permissions = require("../auth/permissions");
|
||||||
if (!settings.disableEditor) {
|
if (!settings.disableEditor) {
|
||||||
Users.default().then(function(anonymousUser) {
|
Users.default().then(function(anonymousUser) {
|
||||||
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
125
red/api/editor/index.js
Normal file
125
red/api/editor/index.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var express = require("express");
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var comms = require("./comms");
|
||||||
|
var library = require("./library");
|
||||||
|
var info = require("./settings");
|
||||||
|
|
||||||
|
var auth = require("../auth");
|
||||||
|
var nodes = require("../admin/nodes"); // TODO: move /icons into here
|
||||||
|
var needsPermission;
|
||||||
|
var runtime;
|
||||||
|
var log;
|
||||||
|
var apiUtil = require("../util");
|
||||||
|
|
||||||
|
var ensureRuntimeStarted = function(req,res,next) {
|
||||||
|
if (!runtime.isStarted()) {
|
||||||
|
log.error("Node-RED runtime not started");
|
||||||
|
res.status(503).send("Not started");
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(server, _runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
log = runtime.log;
|
||||||
|
needsPermission = auth.needsPermission;
|
||||||
|
var settings = runtime.settings;
|
||||||
|
if (!settings.disableEditor) {
|
||||||
|
info.init(runtime);
|
||||||
|
comms.init(server,runtime);
|
||||||
|
|
||||||
|
var ui = require("./ui");
|
||||||
|
ui.init(runtime);
|
||||||
|
var editorApp = express();
|
||||||
|
if (settings.requireHttps === true) {
|
||||||
|
editorApp.enable('trust proxy');
|
||||||
|
editorApp.use(function (req, res, next) {
|
||||||
|
if (req.secure) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect('https://' + req.headers.host + req.originalUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||||
|
|
||||||
|
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,apiUtil.errorHandler);
|
||||||
|
editorApp.get("/icons/:module/:icon",ui.icon);
|
||||||
|
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
|
||||||
|
|
||||||
|
var theme = require("./theme");
|
||||||
|
theme.init(runtime);
|
||||||
|
editorApp.use("/theme",theme.app());
|
||||||
|
editorApp.use("/",ui.editorResources);
|
||||||
|
|
||||||
|
//Projects
|
||||||
|
var projects = require("./projects");
|
||||||
|
projects.init(runtime);
|
||||||
|
editorApp.use("/projects",projects.app());
|
||||||
|
|
||||||
|
// Locales
|
||||||
|
var locales = require("./locales");
|
||||||
|
locales.init(runtime);
|
||||||
|
editorApp.get('/locales/nodes',locales.getAllNodes,apiUtil.errorHandler);
|
||||||
|
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// Library
|
||||||
|
var library = require("./library");
|
||||||
|
library.init(editorApp,runtime);
|
||||||
|
editorApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,apiUtil.errorHandler);
|
||||||
|
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
|
||||||
|
editorApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// Credentials
|
||||||
|
var credentials = require("./credentials");
|
||||||
|
credentials.init(runtime);
|
||||||
|
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
|
||||||
|
// User Settings
|
||||||
|
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
|
||||||
|
// User Settings
|
||||||
|
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// SSH keys
|
||||||
|
var sshkeys = require("./sshkeys");
|
||||||
|
sshkeys.init(runtime);
|
||||||
|
editorApp.use("/settings/user/keys",sshkeys.app());
|
||||||
|
|
||||||
|
return editorApp;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
start: function() {
|
||||||
|
var catalogPath = path.resolve(path.join(__dirname,"locales"));
|
||||||
|
return runtime.i18n.registerMessageCatalogs([
|
||||||
|
{namespace: "editor", dir: catalogPath, file:"editor.json"},
|
||||||
|
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
|
||||||
|
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
|
||||||
|
]).then(function(){
|
||||||
|
comms.start();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop: comms.stop,
|
||||||
|
publish: comms.publish,
|
||||||
|
registerLibrary: library.register
|
||||||
|
}
|
@ -21,7 +21,7 @@ var redApp = null;
|
|||||||
var storage;
|
var storage;
|
||||||
var log;
|
var log;
|
||||||
var redNodes;
|
var redNodes;
|
||||||
var needsPermission = require("./auth").needsPermission;
|
var needsPermission = require("../auth").needsPermission;
|
||||||
|
|
||||||
function createLibrary(type) {
|
function createLibrary(type) {
|
||||||
if (redApp) {
|
if (redApp) {
|
@ -15,17 +15,10 @@
|
|||||||
**/
|
**/
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
//var apiUtil = require('../util');
|
||||||
var i18n;
|
var i18n;
|
||||||
var redNodes;
|
var redNodes;
|
||||||
|
|
||||||
function determineLangFromHeaders(acceptedLanguages){
|
|
||||||
var lang = i18n.defaultLang;
|
|
||||||
acceptedLanguages = acceptedLanguages || [];
|
|
||||||
if (acceptedLanguages.length >= 1) {
|
|
||||||
lang = acceptedLanguages[0];
|
|
||||||
}
|
|
||||||
return lang;
|
|
||||||
}
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(runtime) {
|
init: function(runtime) {
|
||||||
i18n = runtime.i18n;
|
i18n = runtime.i18n;
|
||||||
@ -35,7 +28,7 @@ module.exports = {
|
|||||||
var namespace = req.params[0];
|
var namespace = req.params[0];
|
||||||
var lngs = req.query.lng;
|
var lngs = req.query.lng;
|
||||||
namespace = namespace.replace(/\.json$/,"");
|
namespace = namespace.replace(/\.json$/,"");
|
||||||
var lang = req.query.lng; //determineLangFromHeaders(req.acceptsLanguages() || []);
|
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
|
||||||
var prevLang = i18n.i.lng();
|
var prevLang = i18n.i.lng();
|
||||||
// Trigger a load from disk of the language if it is not the default
|
// Trigger a load from disk of the language if it is not the default
|
||||||
i18n.i.setLng(lang, function(){
|
i18n.i.setLng(lang, function(){
|
||||||
@ -55,6 +48,5 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
res.json(result);
|
res.json(result);
|
||||||
},
|
}
|
||||||
determineLangFromHeaders: determineLangFromHeaders
|
|
||||||
}
|
}
|
@ -76,15 +76,24 @@
|
|||||||
"password": "Password",
|
"password": "Password",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"loginFailed": "Login failed",
|
"loginFailed": "Login failed",
|
||||||
"notAuthorized": "Not authorized"
|
"notAuthorized": "Not authorized",
|
||||||
|
"errors": {
|
||||||
|
"settings": "You must be logged in to access settings",
|
||||||
|
"deploy": "You must be logged in to deploy changes",
|
||||||
|
"notAuthorized": "You must be logged in to perform this action"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"warning": "<strong>Warning</strong>: __message__",
|
"warning": "<strong>Warning</strong>: __message__",
|
||||||
"warnings": {
|
"warnings": {
|
||||||
"undeployedChanges": "node has undeployed changes",
|
"undeployedChanges": "node has undeployed changes",
|
||||||
"nodeActionDisabled": "node actions disabled within subflow",
|
"nodeActionDisabled": "node actions disabled within subflow",
|
||||||
"missing-types": "Flows stopped due to missing node types. Check logs for details.",
|
"missing-types": "<p>Flows stopped due to missing node types.</p>",
|
||||||
"restartRequired": "Node-RED must be restarted to enable upgraded modules"
|
"restartRequired": "Node-RED must be restarted to enable upgraded modules",
|
||||||
|
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
|
||||||
|
"missing_flow_file": "<p>Project flow file not found.</p><p>The project is not configured with a flow file.</p>",
|
||||||
|
"project_empty": "<p>The project is empty.</p><p>Do you want to create a default set of project files?<br/>Otherwise, you will have to manually add files to the project outside of the editor.</p>",
|
||||||
|
"project_not_found": "<p>Project '__project__' not found.</p>"
|
||||||
},
|
},
|
||||||
|
|
||||||
"error": "<strong>Error</strong>: __message__",
|
"error": "<strong>Error</strong>: __message__",
|
||||||
@ -158,7 +167,8 @@
|
|||||||
"backgroundUpdate": "The flows on the server have been updated.",
|
"backgroundUpdate": "The flows on the server have been updated.",
|
||||||
"conflictChecking": "Checking to see if the changes can be merged automatically",
|
"conflictChecking": "Checking to see if the changes can be merged automatically",
|
||||||
"conflictAutoMerge": "The changes include no conflicts and can be merged automatically.",
|
"conflictAutoMerge": "The changes include no conflicts and can be merged automatically.",
|
||||||
"conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed."
|
"conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed.",
|
||||||
|
"plusNMore": "+ __count__ more"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"diff": {
|
"diff": {
|
||||||
@ -336,24 +346,24 @@
|
|||||||
"sortRecent": "recent",
|
"sortRecent": "recent",
|
||||||
"more": "+ __count__ more",
|
"more": "+ __count__ more",
|
||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "Failed to load node catalogue.<br>Check the browser console for more information",
|
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
|
||||||
"installFailed": "Failed to install: __module__<br>__message__<br>Check the log for more information",
|
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"removeFailed": "Failed to remove: __module__<br>__message__<br>Check the log for more information",
|
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"updateFailed": "Failed to update: __module__<br>__message__<br>Check the log for more information",
|
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"enableFailed": "Failed to enable: __module__<br>__message__<br>Check the log for more information",
|
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"disableFailed": "Failed to disable: __module__<br>__message__<br>Check the log for more information"
|
"disableFailed": "<p>Failed to disable: __module__</p><p>__message__</p><p>Check the log for more information</p>"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"install": {
|
"install": {
|
||||||
"body":"Before installing, please read the node's documentation. Some nodes have dependencies that cannot be automatically resolved and can require a restart of Node-RED. ",
|
"body":"<p>Installing '__module__'</p><p>Before installing, please read the node's documentation. Some nodes have dependencies that cannot be automatically resolved and can require a restart of Node-RED.</p>",
|
||||||
"title": "Install nodes"
|
"title": "Install nodes"
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"body":"Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.",
|
"body":"<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
|
||||||
"title": "Remove nodes"
|
"title": "Remove nodes"
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body":"Updating the node will require a restart of Node-RED to complete the update. This must be done manually.",
|
"body":"<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
|
||||||
"title": "Update nodes"
|
"title": "Update nodes"
|
||||||
},
|
},
|
||||||
"cannotUpdate": {
|
"cannotUpdate": {
|
||||||
@ -389,7 +399,12 @@
|
|||||||
"showMore": "show more",
|
"showMore": "show more",
|
||||||
"showLess": "show less",
|
"showLess": "show less",
|
||||||
"flow": "Flow",
|
"flow": "Flow",
|
||||||
"information": "Information",
|
"selection":"Selection",
|
||||||
|
"nodes":"__count__ nodes",
|
||||||
|
"flowDesc": "Flow Description",
|
||||||
|
"subflowDesc": "Subflow Description",
|
||||||
|
"nodeHelp": "Node Help",
|
||||||
|
"none":"None",
|
||||||
"arrayItems": "__count__ items",
|
"arrayItems": "__count__ items",
|
||||||
"showTips":"You can open the tips from the settings panel"
|
"showTips":"You can open the tips from the settings panel"
|
||||||
},
|
},
|
||||||
@ -407,6 +422,15 @@
|
|||||||
"palette": {
|
"palette": {
|
||||||
"name": "Palette management",
|
"name": "Palette management",
|
||||||
"label": "palette"
|
"label": "palette"
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"label": "project",
|
||||||
|
"name": "Project",
|
||||||
|
"description": "Description",
|
||||||
|
"dependencies": "Dependencies",
|
||||||
|
"settings": "Settings",
|
||||||
|
"editDescription": "Edit project description",
|
||||||
|
"editDependencies": "Edit project dependencies"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedInput": {
|
"typedInput": {
|
||||||
@ -450,6 +474,9 @@
|
|||||||
"title": "JSON editor",
|
"title": "JSON editor",
|
||||||
"format": "format JSON"
|
"format": "format JSON"
|
||||||
},
|
},
|
||||||
|
"markdownEditor": {
|
||||||
|
"title": "Markdown editor"
|
||||||
|
},
|
||||||
"bufferEditor": {
|
"bufferEditor": {
|
||||||
"title": "Buffer editor",
|
"title": "Buffer editor",
|
||||||
"modeString": "Handle as UTF-8 String",
|
"modeString": "Handle as UTF-8 String",
|
550
red/api/editor/projects/index.js
Normal file
550
red/api/editor/projects/index.js
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var express = require("express");
|
||||||
|
var runtime;
|
||||||
|
var settings;
|
||||||
|
var needsPermission = require("../../auth").needsPermission;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
settings = runtime.settings;
|
||||||
|
},
|
||||||
|
app: function() {
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
app.use(function(req,res,next) {
|
||||||
|
if (!runtime.storage.projects) {
|
||||||
|
res.status(404).end();
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Projects
|
||||||
|
|
||||||
|
// List all projects
|
||||||
|
app.get("/", needsPermission("projects.read"), function(req,res) {
|
||||||
|
runtime.storage.projects.listProjects(req.user, req.user).then(function(list) {
|
||||||
|
var active = runtime.storage.projects.getActiveProject(req.user);
|
||||||
|
var response = {
|
||||||
|
projects: list
|
||||||
|
};
|
||||||
|
if (active) {
|
||||||
|
response.active = active.name;
|
||||||
|
}
|
||||||
|
res.json(response);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create project
|
||||||
|
app.post("/", needsPermission("projects.write"), function(req,res) {
|
||||||
|
runtime.storage.projects.createProject(req.user, req.body).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update a project
|
||||||
|
app.put("/:id", needsPermission("projects.write"), function(req,res) {
|
||||||
|
//TODO: validate the payload properly
|
||||||
|
if (req.body.active) {
|
||||||
|
var currentProject = runtime.storage.projects.getActiveProject(req.user);
|
||||||
|
if (!currentProject || req.params.id !== currentProject.name) {
|
||||||
|
runtime.storage.projects.setActiveProject(req.user, req.params.id).then(function() {
|
||||||
|
res.redirect(303,req.baseUrl + '/');
|
||||||
|
}).catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||||
|
}
|
||||||
|
} else if (req.body.initialise) {
|
||||||
|
// Initialised set when creating default files for an empty repo
|
||||||
|
runtime.storage.projects.initialiseProject(req.user, req.params.id, req.body).then(function() {
|
||||||
|
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||||
|
}).catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (req.body.hasOwnProperty('credentialSecret') ||
|
||||||
|
req.body.hasOwnProperty('description') ||
|
||||||
|
req.body.hasOwnProperty('dependencies')||
|
||||||
|
req.body.hasOwnProperty('summary') ||
|
||||||
|
req.body.hasOwnProperty('files') ||
|
||||||
|
req.body.hasOwnProperty('git')) {
|
||||||
|
runtime.storage.projects.updateProject(req.user, req.params.id, req.body).then(function() {
|
||||||
|
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||||
|
}).catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:"invalid_request"});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get project metadata
|
||||||
|
app.get("/:id", needsPermission("projects.read"), function(req,res) {
|
||||||
|
runtime.storage.projects.getProject(req.user, req.params.id).then(function(data) {
|
||||||
|
if (data) {
|
||||||
|
res.json(data);
|
||||||
|
} else {
|
||||||
|
res.status(404).end();
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete project
|
||||||
|
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
|
||||||
|
runtime.storage.projects.deleteProject(req.user, req.params.id).then(function() {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Get project status - files, commit counts, branch info
|
||||||
|
app.get("/:id/status", needsPermission("projects.read"), function(req,res) {
|
||||||
|
runtime.storage.projects.getStatus(req.user, req.params.id).then(function(data) {
|
||||||
|
if (data) {
|
||||||
|
res.json(data);
|
||||||
|
} else {
|
||||||
|
res.status(404).end();
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Project file listing
|
||||||
|
app.get("/:id/files", needsPermission("projects.read"), function(req,res) {
|
||||||
|
runtime.storage.projects.getFiles(req.user, req.params.id).then(function(data) {
|
||||||
|
// console.log("TODO: REMOVE /:id/files as /:id/status is better!")
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Get file content in a given tree (index/stage)
|
||||||
|
app.get("/:id/files/:treeish/*", needsPermission("projects.read"), function(req,res) {
|
||||||
|
var projectId = req.params.id;
|
||||||
|
var treeish = req.params.treeish;
|
||||||
|
var filePath = req.params[0];
|
||||||
|
|
||||||
|
runtime.storage.projects.getFile(req.user, projectId,filePath,treeish).then(function(data) {
|
||||||
|
res.json({content:data});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Revert a file
|
||||||
|
app.delete("/:id/files/_/*", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectId = req.params.id;
|
||||||
|
var filePath = req.params[0];
|
||||||
|
|
||||||
|
runtime.storage.projects.revertFile(req.user, projectId,filePath).then(function() {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Stage a file
|
||||||
|
app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var file = req.params[0];
|
||||||
|
|
||||||
|
runtime.storage.projects.stageFile(req.user, projectName,file).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stage multiple files
|
||||||
|
app.post("/:id/stage", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var files = req.body.files;
|
||||||
|
|
||||||
|
runtime.storage.projects.stageFile(req.user, projectName,files).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Commit changes
|
||||||
|
app.post("/:id/commit", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
|
||||||
|
runtime.storage.projects.commit(req.user, projectName,req.body).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unstage a file
|
||||||
|
app.delete("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var file = req.params[0];
|
||||||
|
|
||||||
|
runtime.storage.projects.unstageFile(req.user, projectName,file).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unstage multiple files
|
||||||
|
app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.unstageFile(req.user, projectName).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get a file diff
|
||||||
|
app.get("/:id/diff/:type/*", needsPermission("projects.read"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var type = req.params.type;
|
||||||
|
var file = req.params[0];
|
||||||
|
runtime.storage.projects.getFileDiff(req.user, projectName,file,type).then(function(data) {
|
||||||
|
res.json({
|
||||||
|
diff: data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get a list of commits
|
||||||
|
app.get("/:id/commits", needsPermission("projects.read"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var options = {
|
||||||
|
limit: req.query.limit||20,
|
||||||
|
before: req.query.before
|
||||||
|
};
|
||||||
|
runtime.storage.projects.getCommits(req.user, projectName,options).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get an individual commit details
|
||||||
|
app.get("/:id/commits/:sha", needsPermission("projects.read"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var sha = req.params.sha;
|
||||||
|
|
||||||
|
runtime.storage.projects.getCommit(req.user, projectName,sha).then(function(data) {
|
||||||
|
res.json({commit:data});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Push local commits to remote
|
||||||
|
app.post("/:id/push/?*", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var remoteBranchName = req.params[0]
|
||||||
|
var setRemote = req.query.u;
|
||||||
|
runtime.storage.projects.push(req.user, projectName,remoteBranchName,setRemote).then(function(data) {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pull remote commits
|
||||||
|
app.post("/:id/pull/?*", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var remoteBranchName = req.params[0];
|
||||||
|
var setRemote = req.query.u;
|
||||||
|
runtime.storage.projects.pull(req.user, projectName,remoteBranchName,setRemote).then(function(data) {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Abort an ongoing merge
|
||||||
|
app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.abortMerge(req.user, projectName).then(function(data) {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resolve a merge
|
||||||
|
app.post("/:id/resolve/*", needsPermission("projects.write"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var file = req.params[0];
|
||||||
|
var resolution = req.body.resolutions;
|
||||||
|
runtime.storage.projects.resolveMerge(req.user, projectName,file,resolution).then(function(data) {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get a list of local branches
|
||||||
|
app.get("/:id/branches", needsPermission("projects.read"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.getBranches(req.user, projectName,false).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete a local branch - ?force=true
|
||||||
|
app.delete("/:id/branches/:branchName", needsPermission("projects.write"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var branchName = req.params.branchName;
|
||||||
|
var force = !!req.query.force;
|
||||||
|
runtime.storage.projects.deleteBranch(req.user, projectName, branchName, false, force).then(function(data) {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get a list of remote branches
|
||||||
|
app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.getBranches(req.user, projectName,true).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get branch status - commit counts/ahead/behind
|
||||||
|
app.get("/:id/branches/remote/*/status", needsPermission("projects.read"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var branch = req.params[0];
|
||||||
|
runtime.storage.projects.getBranchStatus(req.user, projectName,branch).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the active local branch
|
||||||
|
app.post("/:id/branches", needsPermission("projects.write"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var branchName = req.body.name;
|
||||||
|
var isCreate = req.body.create;
|
||||||
|
runtime.storage.projects.setBranch(req.user, projectName,branchName,isCreate).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get a list of remotes
|
||||||
|
app.get("/:id/remotes", needsPermission("projects.read"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.getRemotes(req.user, projectName).then(function(data) {
|
||||||
|
res.json(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a remote
|
||||||
|
app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.addRemote(req.user, projectName, req.body).then(function() {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/remotes");
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete a remote
|
||||||
|
app.delete("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var remoteName = req.params.remoteName;
|
||||||
|
runtime.storage.projects.removeRemote(req.user, projectName, remoteName).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/remotes");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update a remote
|
||||||
|
app.put("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
var remoteName = req.params.remoteName;
|
||||||
|
runtime.storage.projects.updateRemote(req.user, projectName, remoteName, req.body).then(function(data) {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
122
red/api/editor/settings.js
Normal file
122
red/api/editor/settings.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
var theme = require("../editor/theme");
|
||||||
|
var util = require('util');
|
||||||
|
var runtime;
|
||||||
|
var settings;
|
||||||
|
var log;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
settings = runtime.settings;
|
||||||
|
log = runtime.log;
|
||||||
|
},
|
||||||
|
runtimeSettings: function(req,res) {
|
||||||
|
var safeSettings = {
|
||||||
|
httpNodeRoot: settings.httpNodeRoot||"/",
|
||||||
|
version: settings.version,
|
||||||
|
user: req.user
|
||||||
|
}
|
||||||
|
|
||||||
|
var themeSettings = theme.settings();
|
||||||
|
if (themeSettings) {
|
||||||
|
safeSettings.editorTheme = themeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (util.isArray(settings.paletteCategories)) {
|
||||||
|
safeSettings.paletteCategories = settings.paletteCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.flowFilePretty) {
|
||||||
|
safeSettings.flowFilePretty = settings.flowFilePretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!runtime.nodes.paletteEditorEnabled()) {
|
||||||
|
safeSettings.editorTheme = safeSettings.editorTheme || {};
|
||||||
|
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
|
||||||
|
safeSettings.editorTheme.palette.editable = false;
|
||||||
|
}
|
||||||
|
if (runtime.storage.projects) {
|
||||||
|
var activeProject = runtime.storage.projects.getActiveProject();
|
||||||
|
if (activeProject) {
|
||||||
|
safeSettings.project = activeProject;
|
||||||
|
}
|
||||||
|
safeSettings.files = {
|
||||||
|
flow: runtime.storage.projects.getFlowFilename(),
|
||||||
|
credentials: runtime.storage.projects.getCredentialsFilename()
|
||||||
|
}
|
||||||
|
safeSettings.git = {
|
||||||
|
globalUser: runtime.storage.projects.getGlobalGitUser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
|
||||||
|
|
||||||
|
settings.exportNodeSettings(safeSettings);
|
||||||
|
res.json(safeSettings);
|
||||||
|
},
|
||||||
|
userSettings: function(req, res) {
|
||||||
|
var username;
|
||||||
|
if (!req.user || req.user.anonymous) {
|
||||||
|
username = '_';
|
||||||
|
} else {
|
||||||
|
username = req.user.username;
|
||||||
|
}
|
||||||
|
res.json(settings.getUserSettings(username)||{});
|
||||||
|
},
|
||||||
|
updateUserSettings: function(req,res) {
|
||||||
|
var username;
|
||||||
|
if (!req.user || req.user.anonymous) {
|
||||||
|
username = '_';
|
||||||
|
} else {
|
||||||
|
username = req.user.username;
|
||||||
|
}
|
||||||
|
var currentSettings = settings.getUserSettings(username)||{};
|
||||||
|
currentSettings = extend(currentSettings, req.body);
|
||||||
|
settings.setUserSettings(username, currentSettings).then(function() {
|
||||||
|
log.audit({event: "settings.update",username:username},req);
|
||||||
|
res.status(204).end();
|
||||||
|
}).otherwise(function(err) {
|
||||||
|
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()},req);
|
||||||
|
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extend(target, source) {
|
||||||
|
var keys = Object.keys(source);
|
||||||
|
var i = keys.length;
|
||||||
|
while(i--) {
|
||||||
|
var value = source[keys[i]]
|
||||||
|
var type = typeof value;
|
||||||
|
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
|
||||||
|
target[keys[i]] = value;
|
||||||
|
} else if (value === null) {
|
||||||
|
if (target.hasOwnProperty(keys[i])) {
|
||||||
|
delete target[keys[i]];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Object
|
||||||
|
if (target.hasOwnProperty(keys[i])) {
|
||||||
|
target[keys[i]] = extend(target[keys[i]],value);
|
||||||
|
} else {
|
||||||
|
target[keys[i]] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
126
red/api/editor/sshkeys.js
Normal file
126
red/api/editor/sshkeys.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var express = require("express");
|
||||||
|
var os = require("os");
|
||||||
|
var runtime;
|
||||||
|
var settings;
|
||||||
|
var needsPermission = require("../auth").needsPermission;
|
||||||
|
|
||||||
|
function getUsername(userObj) {
|
||||||
|
var username = '__default';
|
||||||
|
if ( userObj && userObj.name ) {
|
||||||
|
username = userObj.name;
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
settings = runtime.settings;
|
||||||
|
},
|
||||||
|
app: function() {
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
// SSH keys
|
||||||
|
|
||||||
|
// List all SSH keys
|
||||||
|
app.get("/", needsPermission("settings.read"), function(req,res) {
|
||||||
|
var username = getUsername(req.user);
|
||||||
|
runtime.storage.projects.ssh.listSSHKeys(username)
|
||||||
|
.then(function(list) {
|
||||||
|
res.json({
|
||||||
|
keys: list
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
// console.log(err.stack);
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get SSH key detail
|
||||||
|
app.get("/:id", needsPermission("settings.read"), function(req,res) {
|
||||||
|
var username = getUsername(req.user);
|
||||||
|
// console.log('username:', username);
|
||||||
|
runtime.storage.projects.ssh.getSSHKey(username, req.params.id)
|
||||||
|
.then(function(data) {
|
||||||
|
if (data) {
|
||||||
|
res.json({
|
||||||
|
publickey: data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate a SSH key
|
||||||
|
app.post("/", needsPermission("settings.write"), function(req,res) {
|
||||||
|
var username = getUsername(req.user);
|
||||||
|
// console.log('req.body:', req.body);
|
||||||
|
if ( req.body && req.body.name && /^[a-zA-Z0-9\-_]+$/.test(req.body.name)) {
|
||||||
|
runtime.storage.projects.ssh.generateSSHKey(username, req.body)
|
||||||
|
.then(function(name) {
|
||||||
|
// console.log('generate key --- success name:', name);
|
||||||
|
res.json({
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:"You need to have body or body.name"});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete a SSH key
|
||||||
|
app.delete("/:id", needsPermission("settings.write"), function(req,res) {
|
||||||
|
var username = getUsername(req.user);
|
||||||
|
runtime.storage.projects.ssh.deleteSSHKey(username, req.params.id)
|
||||||
|
.then(function() {
|
||||||
|
res.status(204).end();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
@ -182,6 +182,12 @@ module.exports = {
|
|||||||
if (theme.hasOwnProperty("palette")) {
|
if (theme.hasOwnProperty("palette")) {
|
||||||
themeSettings.palette = theme.palette;
|
themeSettings.palette = theme.palette;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (theme.hasOwnProperty("projects")) {
|
||||||
|
themeSettings.projects = theme.projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return themeApp;
|
return themeApp;
|
||||||
},
|
},
|
||||||
context: function() {
|
context: function() {
|
@ -22,7 +22,7 @@ var theme = require("./theme");
|
|||||||
|
|
||||||
var redNodes;
|
var redNodes;
|
||||||
|
|
||||||
var templateDir = path.resolve(__dirname+"/../../editor/templates");
|
var templateDir = path.resolve(__dirname+"/../../../editor/templates");
|
||||||
var editorTemplate;
|
var editorTemplate;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -52,5 +52,5 @@ module.exports = {
|
|||||||
editor: function(req,res) {
|
editor: function(req,res) {
|
||||||
res.send(Mustache.render(editorTemplate,theme.context()));
|
res.send(Mustache.render(editorTemplate,theme.context()));
|
||||||
},
|
},
|
||||||
editorResources: express.static(__dirname + '/../../public')
|
editorResources: express.static(__dirname + '/../../../public')
|
||||||
};
|
};
|
147
red/api/index.js
147
red/api/index.js
@ -17,49 +17,19 @@
|
|||||||
var express = require("express");
|
var express = require("express");
|
||||||
var bodyParser = require("body-parser");
|
var bodyParser = require("body-parser");
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var path = require('path');
|
|
||||||
var passport = require('passport');
|
var passport = require('passport');
|
||||||
var when = require('when');
|
var when = require('when');
|
||||||
var cors = require('cors');
|
var cors = require('cors');
|
||||||
|
|
||||||
var ui = require("./ui");
|
|
||||||
var nodes = require("./nodes");
|
|
||||||
var flows = require("./flows");
|
|
||||||
var flow = require("./flow");
|
|
||||||
var library = require("./library");
|
|
||||||
var info = require("./info");
|
|
||||||
var theme = require("./theme");
|
|
||||||
var locales = require("./locales");
|
|
||||||
var credentials = require("./credentials");
|
|
||||||
var comms = require("./comms");
|
|
||||||
|
|
||||||
var auth = require("./auth");
|
var auth = require("./auth");
|
||||||
var needsPermission = auth.needsPermission;
|
var apiUtil = require("./util");
|
||||||
|
|
||||||
var i18n;
|
var i18n;
|
||||||
var log;
|
var log;
|
||||||
var adminApp;
|
var adminApp;
|
||||||
var server;
|
var server;
|
||||||
var runtime;
|
var runtime;
|
||||||
|
var editor;
|
||||||
var errorHandler = function(err,req,res,next) {
|
|
||||||
if (err.message === "request entity too large") {
|
|
||||||
log.error(err);
|
|
||||||
} else {
|
|
||||||
console.log(err.stack);
|
|
||||||
}
|
|
||||||
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.toString()},req);
|
|
||||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
|
||||||
};
|
|
||||||
|
|
||||||
var ensureRuntimeStarted = function(req,res,next) {
|
|
||||||
if (!runtime.isStarted()) {
|
|
||||||
log.error("Node-RED runtime not started");
|
|
||||||
res.status(503).send("Not started");
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init(_server,_runtime) {
|
function init(_server,_runtime) {
|
||||||
server = _server;
|
server = _server;
|
||||||
@ -68,46 +38,15 @@ function init(_server,_runtime) {
|
|||||||
i18n = runtime.i18n;
|
i18n = runtime.i18n;
|
||||||
log = runtime.log;
|
log = runtime.log;
|
||||||
if (settings.httpAdminRoot !== false) {
|
if (settings.httpAdminRoot !== false) {
|
||||||
comms.init(server,runtime);
|
apiUtil.init(runtime);
|
||||||
adminApp = express();
|
adminApp = express();
|
||||||
auth.init(runtime);
|
auth.init(runtime);
|
||||||
credentials.init(runtime);
|
|
||||||
flows.init(runtime);
|
|
||||||
flow.init(runtime);
|
|
||||||
info.init(runtime);
|
|
||||||
library.init(adminApp,runtime);
|
|
||||||
locales.init(runtime);
|
|
||||||
nodes.init(runtime);
|
|
||||||
|
|
||||||
// Editor
|
|
||||||
if (!settings.disableEditor) {
|
|
||||||
ui.init(runtime);
|
|
||||||
var editorApp = express();
|
|
||||||
if (settings.requireHttps === true) {
|
|
||||||
editorApp.enable('trust proxy');
|
|
||||||
editorApp.use(function (req, res, next) {
|
|
||||||
if (req.secure) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.redirect('https://' + req.headers.host + req.originalUrl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
|
||||||
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,errorHandler);
|
|
||||||
editorApp.get("/icons/:module/:icon",ui.icon);
|
|
||||||
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
|
|
||||||
theme.init(runtime);
|
|
||||||
editorApp.use("/theme",theme.app());
|
|
||||||
editorApp.use("/",ui.editorResources);
|
|
||||||
adminApp.use(editorApp);
|
|
||||||
}
|
|
||||||
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
||||||
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
|
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
|
||||||
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
|
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
|
||||||
|
|
||||||
adminApp.get("/auth/login",auth.login,errorHandler);
|
adminApp.get("/auth/login",auth.login,apiUtil.errorHandler);
|
||||||
|
|
||||||
if (settings.adminAuth) {
|
if (settings.adminAuth) {
|
||||||
if (settings.adminAuth.type === "strategy") {
|
if (settings.adminAuth.type === "strategy") {
|
||||||
auth.genericStrategy(adminApp,settings.adminAuth.strategy);
|
auth.genericStrategy(adminApp,settings.adminAuth.strategy);
|
||||||
@ -120,62 +59,38 @@ function init(_server,_runtime) {
|
|||||||
auth.errorHandler
|
auth.errorHandler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke,errorHandler);
|
adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
if (!settings.disableEditor) {
|
||||||
|
editor = require("./editor");
|
||||||
|
var editorApp = editor.init(server, runtime);
|
||||||
|
adminApp.use(editorApp);
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.httpAdminCors) {
|
if (settings.httpAdminCors) {
|
||||||
var corsHandler = cors(settings.httpAdminCors);
|
var corsHandler = cors(settings.httpAdminCors);
|
||||||
adminApp.use(corsHandler);
|
adminApp.use(corsHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flows
|
var adminApiApp = require("./admin").init(runtime);
|
||||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,errorHandler);
|
adminApp.use(adminApiApp);
|
||||||
adminApp.post("/flows",needsPermission("flows.write"),flows.post,errorHandler);
|
} else {
|
||||||
|
adminApp = null;
|
||||||
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,errorHandler);
|
|
||||||
adminApp.post("/flow",needsPermission("flows.write"),flow.post,errorHandler);
|
|
||||||
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete,errorHandler);
|
|
||||||
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put,errorHandler);
|
|
||||||
|
|
||||||
// Nodes
|
|
||||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,errorHandler);
|
|
||||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,errorHandler);
|
|
||||||
|
|
||||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,errorHandler);
|
|
||||||
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,errorHandler);
|
|
||||||
adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,errorHandler);
|
|
||||||
|
|
||||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,errorHandler);
|
|
||||||
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,errorHandler);
|
|
||||||
|
|
||||||
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,errorHandler);
|
|
||||||
|
|
||||||
adminApp.get('/locales/nodes',locales.getAllNodes,errorHandler);
|
|
||||||
adminApp.get(/locales\/(.+)\/?$/,locales.get,errorHandler);
|
|
||||||
|
|
||||||
// Library
|
|
||||||
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,errorHandler);
|
|
||||||
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll,errorHandler);
|
|
||||||
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,errorHandler);
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
adminApp.get("/settings",needsPermission("settings.read"),info.settings,errorHandler);
|
|
||||||
|
|
||||||
// Error Handler
|
|
||||||
//adminApp.use(errorHandler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function start() {
|
function start() {
|
||||||
var catalogPath = path.resolve(path.join(__dirname,"locales"));
|
if (editor) {
|
||||||
return i18n.registerMessageCatalogs([
|
return editor.start();
|
||||||
{namespace: "editor", dir: catalogPath, file:"editor.json"},
|
} else {
|
||||||
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
|
return when.resolve();
|
||||||
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
|
}
|
||||||
]).then(function(){
|
|
||||||
comms.start();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function stop() {
|
function stop() {
|
||||||
comms.stop();
|
if (editor) {
|
||||||
|
editor.stop();
|
||||||
|
}
|
||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -183,13 +98,21 @@ module.exports = {
|
|||||||
start: start,
|
start: start,
|
||||||
stop: stop,
|
stop: stop,
|
||||||
library: {
|
library: {
|
||||||
register: library.register
|
register: function(type) {
|
||||||
|
if (editor) {
|
||||||
|
editor.registerLibrary(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
needsPermission: auth.needsPermission
|
needsPermission: auth.needsPermission
|
||||||
},
|
},
|
||||||
comms: {
|
comms: {
|
||||||
publish: comms.publish
|
publish: function(topic,data,retain) {
|
||||||
|
if (editor) {
|
||||||
|
editor.publish(topic,data,retain);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
get adminApp() { return adminApp; },
|
get adminApp() { return adminApp; },
|
||||||
get server() { return server; }
|
get server() { return server; }
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
**/
|
|
||||||
var theme = require("./theme");
|
|
||||||
var util = require('util');
|
|
||||||
var runtime;
|
|
||||||
var settings;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
init: function(_runtime) {
|
|
||||||
runtime = _runtime;
|
|
||||||
settings = runtime.settings;
|
|
||||||
},
|
|
||||||
settings: function(req,res) {
|
|
||||||
var safeSettings = {
|
|
||||||
httpNodeRoot: settings.httpNodeRoot||"/",
|
|
||||||
version: settings.version,
|
|
||||||
user: req.user
|
|
||||||
}
|
|
||||||
|
|
||||||
var themeSettings = theme.settings();
|
|
||||||
if (themeSettings) {
|
|
||||||
safeSettings.editorTheme = themeSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (util.isArray(settings.paletteCategories)) {
|
|
||||||
safeSettings.paletteCategories = settings.paletteCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.flowFilePretty) {
|
|
||||||
safeSettings.flowFilePretty = settings.flowFilePretty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!runtime.nodes.paletteEditorEnabled()) {
|
|
||||||
safeSettings.editorTheme = safeSettings.editorTheme || {};
|
|
||||||
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
|
|
||||||
safeSettings.editorTheme.palette.editable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.exportNodeSettings(safeSettings);
|
|
||||||
|
|
||||||
res.json(safeSettings);
|
|
||||||
}
|
|
||||||
}
|
|
45
red/api/util.js
Normal file
45
red/api/util.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
var i18n;
|
||||||
|
var log;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_runtime) {
|
||||||
|
log = _runtime.log;
|
||||||
|
i18n = _runtime.i18n;
|
||||||
|
},
|
||||||
|
errorHandler: function(err,req,res,next) {
|
||||||
|
console.error(err.stack);
|
||||||
|
if (err.message === "request entity too large") {
|
||||||
|
log.error(err);
|
||||||
|
} else {
|
||||||
|
log.error(err.stack);
|
||||||
|
}
|
||||||
|
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
},
|
||||||
|
|
||||||
|
determineLangFromHeaders: function(acceptedLanguages){
|
||||||
|
var lang = i18n.defaultLang;
|
||||||
|
acceptedLanguages = acceptedLanguages || [];
|
||||||
|
if (acceptedLanguages.length >= 1) {
|
||||||
|
lang = acceptedLanguages[0];
|
||||||
|
}
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
}
|
@ -162,9 +162,9 @@ function start() {
|
|||||||
if (settings.httpStatic) {
|
if (settings.httpStatic) {
|
||||||
log.info(log._("runtime.paths.httpStatic",{path:path.resolve(settings.httpStatic)}));
|
log.info(log._("runtime.paths.httpStatic",{path:path.resolve(settings.httpStatic)}));
|
||||||
}
|
}
|
||||||
redNodes.loadFlows().then(redNodes.startFlows);
|
redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {});
|
||||||
started = true;
|
started = true;
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -139,7 +139,17 @@
|
|||||||
"invalid": "Existing __type__ file is not valid json",
|
"invalid": "Existing __type__ file is not valid json",
|
||||||
"restore": "Restoring __type__ file backup : __path__",
|
"restore": "Restoring __type__ file backup : __path__",
|
||||||
"restore-fail": "Restoring __type__ file backup failed : __message__",
|
"restore-fail": "Restoring __type__ file backup failed : __message__",
|
||||||
"fsync-fail": "Flushing file __path__ to disk failed : __message__"
|
"fsync-fail": "Flushing file __path__ to disk failed : __message__",
|
||||||
|
"projects": {
|
||||||
|
"changing-project": "Setting active project : __project__",
|
||||||
|
"active-project": "Active project : __project__",
|
||||||
|
"project-not-found": "Project not found : __project__",
|
||||||
|
"no-active-project": "No active project : using default flows file",
|
||||||
|
"disabled": "Projects disabled : editorTheme.projects.enabled=false",
|
||||||
|
"disabledNoFlag": "Projects disabled : set editorTheme.projects.enabled=true to enable",
|
||||||
|
"git-not-found": "Projects disabled : git command not found",
|
||||||
|
"git-version-old": "Projects disabled : git __version__ too old"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
var when = require("when");
|
var when = require("when");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
var runtime;
|
||||||
var settings;
|
var settings;
|
||||||
var log;
|
var log;
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ var dirty = false;
|
|||||||
|
|
||||||
var removeDefaultKey = false;
|
var removeDefaultKey = false;
|
||||||
var encryptionEnabled = null;
|
var encryptionEnabled = null;
|
||||||
|
var encryptionKeyType; // disabled, system, user, project
|
||||||
var encryptionAlgorithm = "aes-256-ctr";
|
var encryptionAlgorithm = "aes-256-ctr";
|
||||||
var encryptionKey;
|
var encryptionKey;
|
||||||
|
|
||||||
@ -37,9 +39,15 @@ function decryptCredentials(key,credentials) {
|
|||||||
var decrypted = decipher.update(creds, 'base64', 'utf8') + decipher.final('utf8');
|
var decrypted = decipher.update(creds, 'base64', 'utf8') + decipher.final('utf8');
|
||||||
return JSON.parse(decrypted);
|
return JSON.parse(decrypted);
|
||||||
}
|
}
|
||||||
|
function encryptCredentials(key,credentials) {
|
||||||
|
var initVector = crypto.randomBytes(16);
|
||||||
|
var cipher = crypto.createCipheriv(encryptionAlgorithm, key, initVector);
|
||||||
|
return {"$":initVector.toString('hex') + cipher.update(JSON.stringify(credentials), 'utf8', 'base64') + cipher.final('base64')};
|
||||||
|
}
|
||||||
|
|
||||||
var api = module.exports = {
|
var api = module.exports = {
|
||||||
init: function(runtime) {
|
init: function(_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
log = runtime.log;
|
log = runtime.log;
|
||||||
settings = runtime.settings;
|
settings = runtime.settings;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
@ -53,12 +61,46 @@ var api = module.exports = {
|
|||||||
*/
|
*/
|
||||||
load: function (credentials) {
|
load: function (credentials) {
|
||||||
dirty = false;
|
dirty = false;
|
||||||
/*
|
|
||||||
- if encryptionEnabled === null, check the current configuration
|
|
||||||
*/
|
|
||||||
var credentialsEncrypted = credentials.hasOwnProperty("$") && Object.keys(credentials).length === 1;
|
var credentialsEncrypted = credentials.hasOwnProperty("$") && Object.keys(credentials).length === 1;
|
||||||
|
|
||||||
|
// Case 1: Active Project in place
|
||||||
|
// - use whatever its config says
|
||||||
|
|
||||||
|
// Case 2: _credentialSecret unset, credentialSecret unset
|
||||||
|
// - generate _credentialSecret and encrypt
|
||||||
|
|
||||||
|
// Case 3: _credentialSecret set, credentialSecret set
|
||||||
|
// - migrate from _credentialSecret to credentialSecret
|
||||||
|
// - delete _credentialSecret
|
||||||
|
|
||||||
|
// Case 4: credentialSecret set
|
||||||
|
// - use it
|
||||||
|
|
||||||
var setupEncryptionPromise = when.resolve();
|
var setupEncryptionPromise = when.resolve();
|
||||||
if (encryptionEnabled === null) {
|
|
||||||
|
var projectKey = false;
|
||||||
|
var activeProject;
|
||||||
|
encryptionKeyType = "";
|
||||||
|
|
||||||
|
if (runtime.storage && runtime.storage.projects) {
|
||||||
|
// projects enabled
|
||||||
|
activeProject = runtime.storage.projects.getActiveProject();
|
||||||
|
if (activeProject) {
|
||||||
|
projectKey = activeProject.credentialSecret;
|
||||||
|
if (!projectKey) {
|
||||||
|
log.debug("red/runtime/nodes/credentials.load : using active project key - disabled");
|
||||||
|
encryptionKeyType = "disabled";
|
||||||
|
encryptionEnabled = false;
|
||||||
|
} else {
|
||||||
|
log.debug("red/runtime/nodes/credentials.load : using active project key");
|
||||||
|
encryptionKeyType = "project";
|
||||||
|
encryptionKey = crypto.createHash('sha256').update(projectKey).digest();
|
||||||
|
encryptionEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptionKeyType === '') {
|
||||||
var defaultKey;
|
var defaultKey;
|
||||||
try {
|
try {
|
||||||
defaultKey = settings.get('_credentialSecret');
|
defaultKey = settings.get('_credentialSecret');
|
||||||
@ -66,6 +108,7 @@ var api = module.exports = {
|
|||||||
}
|
}
|
||||||
if (defaultKey) {
|
if (defaultKey) {
|
||||||
defaultKey = crypto.createHash('sha256').update(defaultKey).digest();
|
defaultKey = crypto.createHash('sha256').update(defaultKey).digest();
|
||||||
|
encryptionKeyType = "system";
|
||||||
}
|
}
|
||||||
var userKey;
|
var userKey;
|
||||||
try {
|
try {
|
||||||
@ -73,7 +116,9 @@ var api = module.exports = {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
userKey = false;
|
userKey = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userKey === false) {
|
if (userKey === false) {
|
||||||
|
encryptionKeyType = "disabled";
|
||||||
log.debug("red/runtime/nodes/credentials.load : user disabled encryption");
|
log.debug("red/runtime/nodes/credentials.load : user disabled encryption");
|
||||||
// User has disabled encryption
|
// User has disabled encryption
|
||||||
encryptionEnabled = false;
|
encryptionEnabled = false;
|
||||||
@ -86,18 +131,26 @@ var api = module.exports = {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
credentials = {};
|
credentials = {};
|
||||||
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
||||||
|
var error = new Error("Failed to decrypt credentials");
|
||||||
|
error.code = "credentials_load_failed";
|
||||||
|
return when.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
removeDefaultKey = true;
|
removeDefaultKey = true;
|
||||||
}
|
}
|
||||||
} else if (typeof userKey === 'string') {
|
} else if (typeof userKey === 'string') {
|
||||||
log.debug("red/runtime/nodes/credentials.load : user provided key");
|
if (!projectKey) {
|
||||||
|
log.debug("red/runtime/nodes/credentials.load : user provided key");
|
||||||
|
}
|
||||||
|
if (encryptionKeyType !== 'project') {
|
||||||
|
encryptionKeyType = 'user';
|
||||||
|
}
|
||||||
// User has provided own encryption key, get the 32-byte hash of it
|
// User has provided own encryption key, get the 32-byte hash of it
|
||||||
encryptionKey = crypto.createHash('sha256').update(userKey).digest();
|
encryptionKey = crypto.createHash('sha256').update(userKey).digest();
|
||||||
encryptionEnabled = true;
|
encryptionEnabled = true;
|
||||||
|
|
||||||
if (defaultKey) {
|
if (encryptionKeyType !== 'project' && defaultKey) {
|
||||||
log.debug("red/runtime/nodes/credentials.load : default key present. Will migrate");
|
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
|
// User has provided their own key, but we already have a default key
|
||||||
// Decrypt using default key
|
// Decrypt using default key
|
||||||
@ -107,6 +160,9 @@ var api = module.exports = {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
credentials = {};
|
credentials = {};
|
||||||
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
||||||
|
var error = new Error("Failed to decrypt credentials");
|
||||||
|
error.code = "credentials_load_failed";
|
||||||
|
return when.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
@ -115,6 +171,9 @@ var api = module.exports = {
|
|||||||
} else {
|
} else {
|
||||||
log.debug("red/runtime/nodes/credentials.load : no user key present");
|
log.debug("red/runtime/nodes/credentials.load : no user key present");
|
||||||
// User has not provide their own key
|
// User has not provide their own key
|
||||||
|
if (encryptionKeyType !== 'project') {
|
||||||
|
encryptionKeyType = 'system';
|
||||||
|
}
|
||||||
encryptionKey = defaultKey;
|
encryptionKey = defaultKey;
|
||||||
encryptionEnabled = true;
|
encryptionEnabled = true;
|
||||||
if (encryptionKey === undefined) {
|
if (encryptionKey === undefined) {
|
||||||
@ -137,22 +196,54 @@ var api = module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encryptionEnabled && !dirty) {
|
if (encryptionEnabled && !dirty) {
|
||||||
encryptedCredentials = credentials;
|
encryptedCredentials = credentials;
|
||||||
}
|
}
|
||||||
|
log.debug("red/runtime/nodes/credentials.load : keyType="+encryptionKeyType);
|
||||||
return setupEncryptionPromise.then(function() {
|
return setupEncryptionPromise.then(function() {
|
||||||
|
var clearInvalidFlag = false;
|
||||||
if (credentials.hasOwnProperty("$")) {
|
if (credentials.hasOwnProperty("$")) {
|
||||||
|
if (encryptionEnabled === false) {
|
||||||
|
// The credentials appear to be encrypted, but our config
|
||||||
|
// thinks they are not.
|
||||||
|
var error = new Error("Failed to decrypt credentials");
|
||||||
|
error.code = "credentials_load_failed";
|
||||||
|
if (activeProject) {
|
||||||
|
// This is a project with a bad key. Mark it as invalid
|
||||||
|
// TODO: this delves too deep into Project structure
|
||||||
|
activeProject.credentialSecretInvalid = true;
|
||||||
|
return when.reject(error);
|
||||||
|
}
|
||||||
|
return when.reject(error);
|
||||||
|
}
|
||||||
// These are encrypted credentials
|
// These are encrypted credentials
|
||||||
try {
|
try {
|
||||||
credentialCache = decryptCredentials(encryptionKey,credentials)
|
credentialCache = decryptCredentials(encryptionKey,credentials)
|
||||||
|
clearInvalidFlag = true;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
credentialCache = {};
|
credentialCache = {};
|
||||||
dirty = true;
|
dirty = true;
|
||||||
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
||||||
|
var error = new Error("Failed to decrypt credentials");
|
||||||
|
error.code = "credentials_load_failed";
|
||||||
|
if (activeProject) {
|
||||||
|
// This is a project with a bad key. Mark it as invalid
|
||||||
|
// TODO: this delves too deep into Project structure
|
||||||
|
activeProject.credentialSecretInvalid = true;
|
||||||
|
return when.reject(error);
|
||||||
|
}
|
||||||
|
return when.reject(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
credentialCache = credentials;
|
credentialCache = credentials;
|
||||||
}
|
}
|
||||||
|
if (clearInvalidFlag) {
|
||||||
|
// TODO: this delves too deep into Project structure
|
||||||
|
if (activeProject) {
|
||||||
|
delete activeProject.credentialSecretInvalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -189,6 +280,10 @@ var api = module.exports = {
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clear: function() {
|
||||||
|
credentialCache = {};
|
||||||
|
dirty = true;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Deletes any credentials for nodes that no longer exist
|
* Deletes any credentials for nodes that no longer exist
|
||||||
* @param config a flow config
|
* @param config a flow config
|
||||||
@ -288,16 +383,30 @@ var api = module.exports = {
|
|||||||
dirty: function() {
|
dirty: function() {
|
||||||
return dirty;
|
return dirty;
|
||||||
},
|
},
|
||||||
|
setKey: function(key) {
|
||||||
|
if (key) {
|
||||||
|
encryptionKey = crypto.createHash('sha256').update(key).digest();
|
||||||
|
encryptionEnabled = true;
|
||||||
|
dirty = true;
|
||||||
|
encryptionKeyType = "project";
|
||||||
|
} else {
|
||||||
|
encryptionKey = null;
|
||||||
|
encryptionEnabled = false;
|
||||||
|
dirty = true;
|
||||||
|
encryptionKeyType = "disabled";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getKeyType: function() {
|
||||||
|
return encryptionKeyType;
|
||||||
|
},
|
||||||
export: function() {
|
export: function() {
|
||||||
var result = credentialCache;
|
var result = credentialCache;
|
||||||
|
|
||||||
if (encryptionEnabled) {
|
if (encryptionEnabled) {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
try {
|
try {
|
||||||
log.debug("red/runtime/nodes/credentials.export : encrypting");
|
log.debug("red/runtime/nodes/credentials.export : encrypting");
|
||||||
var initVector = crypto.randomBytes(16);
|
result = encryptCredentials(encryptionKey, credentialCache);
|
||||||
var cipher = crypto.createCipheriv(encryptionAlgorithm, encryptionKey, initVector);
|
|
||||||
result = {"$":initVector.toString('hex') + cipher.update(JSON.stringify(credentialCache), 'utf8', 'base64') + cipher.final('base64')};
|
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
log.warn(log._("nodes.credentials.error-saving",{message:err.toString()}))
|
log.warn(log._("nodes.credentials.error-saving",{message:err.toString()}))
|
||||||
}
|
}
|
||||||
|
@ -73,15 +73,22 @@ function loadFlows() {
|
|||||||
return storage.getFlows().then(function(config) {
|
return storage.getFlows().then(function(config) {
|
||||||
log.debug("loaded flow revision: "+config.rev);
|
log.debug("loaded flow revision: "+config.rev);
|
||||||
return credentials.load(config.credentials).then(function() {
|
return credentials.load(config.credentials).then(function() {
|
||||||
|
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
activeConfig = null;
|
||||||
console.log(err.stack);
|
events.emit("runtime-event",{id:"runtime-state",payload:{type:"warning",error:err.code,project:err.project,text:"notification.warnings."+err.code},retain:true});
|
||||||
|
if (err.code === "project_not_found") {
|
||||||
|
log.warn(log._("storage.localfilesystem.projects.project-not-found",{project:err.project}));
|
||||||
|
} else {
|
||||||
|
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function load() {
|
function load(forceStart) {
|
||||||
return setFlows(null,"load",false);
|
return setFlows(null,"load",false,forceStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -89,7 +96,7 @@ function load() {
|
|||||||
* type - full/nodes/flows/load (default full)
|
* type - full/nodes/flows/load (default full)
|
||||||
* muteLog - don't emit the standard log messages (used for individual flow api)
|
* muteLog - don't emit the standard log messages (used for individual flow api)
|
||||||
*/
|
*/
|
||||||
function setFlows(_config,type,muteLog) {
|
function setFlows(_config,type,muteLog,forceStart) {
|
||||||
type = type||"full";
|
type = type||"full";
|
||||||
|
|
||||||
var configSavePromise = null;
|
var configSavePromise = null;
|
||||||
@ -131,14 +138,14 @@ function setFlows(_config,type,muteLog) {
|
|||||||
rev:flowRevision
|
rev:flowRevision
|
||||||
};
|
};
|
||||||
activeFlowConfig = newFlowConfig;
|
activeFlowConfig = newFlowConfig;
|
||||||
if (started) {
|
if (forceStart || started) {
|
||||||
return stop(type,diff,muteLog).then(function() {
|
return stop(type,diff,muteLog).then(function() {
|
||||||
context.clean(activeFlowConfig);
|
context.clean(activeFlowConfig);
|
||||||
start(type,diff,muteLog).then(function() {
|
start(type,diff,muteLog).then(function() {
|
||||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||||
});
|
});
|
||||||
return flowRevision;
|
return flowRevision;
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||||
@ -251,7 +258,7 @@ function start(type,diff,muteLog) {
|
|||||||
log.info(log._("nodes.flows.missing-type-install-2"));
|
log.info(log._("nodes.flows.missing-type-install-2"));
|
||||||
log.info(" "+settings.userDir);
|
log.info(" "+settings.userDir);
|
||||||
}
|
}
|
||||||
events.emit("runtime-event",{id:"runtime-state",payload:{type:"warning",text:"notification.warnings.missing-types"},retain:true});
|
events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true});
|
||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
if (!muteLog) {
|
if (!muteLog) {
|
||||||
@ -323,6 +330,9 @@ function start(type,diff,muteLog) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stop(type,diff,muteLog) {
|
function stop(type,diff,muteLog) {
|
||||||
|
if (!started) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
type = type||"full";
|
type = type||"full";
|
||||||
diff = diff||{
|
diff = diff||{
|
||||||
added:[],
|
added:[],
|
||||||
|
@ -173,5 +173,9 @@ module.exports = {
|
|||||||
addCredentials: credentials.add,
|
addCredentials: credentials.add,
|
||||||
getCredentials: credentials.get,
|
getCredentials: credentials.get,
|
||||||
deleteCredentials: credentials.delete,
|
deleteCredentials: credentials.delete,
|
||||||
getCredentialDefinition: credentials.getDefinition
|
getCredentialDefinition: credentials.getDefinition,
|
||||||
|
setCredentialSecret: credentials.setKey,
|
||||||
|
clearCredentials: credentials.clear,
|
||||||
|
exportCredentials: credentials.export,
|
||||||
|
getCredentialKeyType: credentials.getKeyType
|
||||||
};
|
};
|
||||||
|
@ -97,7 +97,9 @@ function installModule(module,version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||||
var child = child_process.spawn(npmCommand,['install','--save','--save-prefix="~"','--production',installName],{
|
var args = ['install','--save','--save-prefix="~"','--production',installName];
|
||||||
|
log.trace(npmCommand + JSON.stringify(args));
|
||||||
|
var child = child_process.spawn(npmCommand,args,{
|
||||||
cwd: installDir,
|
cwd: installDir,
|
||||||
shell: true
|
shell: true
|
||||||
});
|
});
|
||||||
@ -190,7 +192,11 @@ function uninstallModule(module) {
|
|||||||
|
|
||||||
var list = registry.removeModule(module);
|
var list = registry.removeModule(module);
|
||||||
log.info(log._("server.install.uninstalling",{name:module}));
|
log.info(log._("server.install.uninstalling",{name:module}));
|
||||||
var child = child_process.execFile(npmCommand,['remove','--save',module],
|
|
||||||
|
var args = ['remove','--save',module];
|
||||||
|
log.trace(npmCommand + JSON.stringify(args));
|
||||||
|
|
||||||
|
var child = child_process.execFile(npmCommand,args,
|
||||||
{
|
{
|
||||||
cwd: installDir
|
cwd: installDir
|
||||||
},
|
},
|
||||||
|
@ -20,22 +20,29 @@ var assert = require("assert");
|
|||||||
var log = require("./log");
|
var log = require("./log");
|
||||||
var util = require("./util");
|
var util = require("./util");
|
||||||
|
|
||||||
var userSettings = null;
|
// localSettings are those provided in the runtime settings.js file
|
||||||
|
var localSettings = null;
|
||||||
|
// globalSettings are provided by storage - .config.json on localfilesystem
|
||||||
var globalSettings = null;
|
var globalSettings = null;
|
||||||
|
// nodeSettings are those settings that a node module defines as being available
|
||||||
var nodeSettings = null;
|
var nodeSettings = null;
|
||||||
|
|
||||||
|
// A subset of globalSettings that deal with per-user settings
|
||||||
|
var userSettings = null;
|
||||||
|
|
||||||
var disableNodeSettings = null;
|
var disableNodeSettings = null;
|
||||||
var storage = null;
|
var storage = null;
|
||||||
|
|
||||||
var persistentSettings = {
|
var persistentSettings = {
|
||||||
init: function(settings) {
|
init: function(settings) {
|
||||||
userSettings = settings;
|
localSettings = settings;
|
||||||
for (var i in settings) {
|
for (var i in settings) {
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (settings.hasOwnProperty(i) && i !== 'load' && i !== 'get' && i !== 'set' && i !== 'available' && i !== 'reset') {
|
if (settings.hasOwnProperty(i) && i !== 'load' && i !== 'get' && i !== 'set' && i !== 'available' && i !== 'reset') {
|
||||||
// Don't allow any of the core functions get replaced via settings
|
// Don't allow any of the core functions get replaced via settings
|
||||||
(function() {
|
(function() {
|
||||||
var j = i;
|
var j = i;
|
||||||
persistentSettings.__defineGetter__(j,function() { return userSettings[j]; });
|
persistentSettings.__defineGetter__(j,function() { return localSettings[j]; });
|
||||||
persistentSettings.__defineSetter__(j,function() { throw new Error("Property '"+j+"' is read-only"); });
|
persistentSettings.__defineSetter__(j,function() { throw new Error("Property '"+j+"' is read-only"); });
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -48,11 +55,20 @@ var persistentSettings = {
|
|||||||
storage = _storage;
|
storage = _storage;
|
||||||
return storage.getSettings().then(function(_settings) {
|
return storage.getSettings().then(function(_settings) {
|
||||||
globalSettings = _settings;
|
globalSettings = _settings;
|
||||||
|
if (globalSettings) {
|
||||||
|
userSettings = globalSettings.users || {};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
userSettings = {};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
get: function(prop) {
|
get: function(prop) {
|
||||||
if (userSettings.hasOwnProperty(prop)) {
|
if (prop === 'users') {
|
||||||
return clone(userSettings[prop]);
|
throw new Error("Do not access user settings directly. Use settings.getUserSettings");
|
||||||
|
}
|
||||||
|
if (localSettings.hasOwnProperty(prop)) {
|
||||||
|
return clone(localSettings[prop]);
|
||||||
}
|
}
|
||||||
if (globalSettings === null) {
|
if (globalSettings === null) {
|
||||||
throw new Error(log._("settings.not-available"));
|
throw new Error(log._("settings.not-available"));
|
||||||
@ -61,7 +77,10 @@ var persistentSettings = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function(prop,value) {
|
set: function(prop,value) {
|
||||||
if (userSettings.hasOwnProperty(prop)) {
|
if (prop === 'users') {
|
||||||
|
throw new Error("Do not access user settings directly. Use settings.setUserSettings");
|
||||||
|
}
|
||||||
|
if (localSettings.hasOwnProperty(prop)) {
|
||||||
throw new Error(log._("settings.property-read-only", {prop:prop}));
|
throw new Error(log._("settings.property-read-only", {prop:prop}));
|
||||||
}
|
}
|
||||||
if (globalSettings === null) {
|
if (globalSettings === null) {
|
||||||
@ -77,7 +96,7 @@ var persistentSettings = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
delete: function(prop) {
|
delete: function(prop) {
|
||||||
if (userSettings.hasOwnProperty(prop)) {
|
if (localSettings.hasOwnProperty(prop)) {
|
||||||
throw new Error(log._("settings.property-read-only", {prop:prop}));
|
throw new Error(log._("settings.property-read-only", {prop:prop}));
|
||||||
}
|
}
|
||||||
if (globalSettings === null) {
|
if (globalSettings === null) {
|
||||||
@ -95,14 +114,15 @@ var persistentSettings = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reset: function() {
|
reset: function() {
|
||||||
for (var i in userSettings) {
|
for (var i in localSettings) {
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (userSettings.hasOwnProperty(i)) {
|
if (localSettings.hasOwnProperty(i)) {
|
||||||
delete persistentSettings[i];
|
delete persistentSettings[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userSettings = null;
|
localSettings = null;
|
||||||
globalSettings = null;
|
globalSettings = null;
|
||||||
|
userSettings = null;
|
||||||
storage = null;
|
storage = null;
|
||||||
},
|
},
|
||||||
registerNodeSettings: function(type, opts) {
|
registerNodeSettings: function(type, opts) {
|
||||||
@ -126,8 +146,8 @@ var persistentSettings = {
|
|||||||
if (setting.exportable) {
|
if (setting.exportable) {
|
||||||
if (safeSettings.hasOwnProperty(property)) {
|
if (safeSettings.hasOwnProperty(property)) {
|
||||||
// Cannot overwrite existing setting
|
// Cannot overwrite existing setting
|
||||||
} else if (userSettings.hasOwnProperty(property)) {
|
} else if (localSettings.hasOwnProperty(property)) {
|
||||||
safeSettings[property] = userSettings[property];
|
safeSettings[property] = localSettings[property];
|
||||||
} else if (setting.hasOwnProperty('value')) {
|
} else if (setting.hasOwnProperty('value')) {
|
||||||
safeSettings[property] = setting.value;
|
safeSettings[property] = setting.value;
|
||||||
}
|
}
|
||||||
@ -148,6 +168,20 @@ var persistentSettings = {
|
|||||||
types.forEach(function(type) {
|
types.forEach(function(type) {
|
||||||
disableNodeSettings[type] = true;
|
disableNodeSettings[type] = true;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
getUserSettings: function(username) {
|
||||||
|
return clone(userSettings[username]);
|
||||||
|
},
|
||||||
|
setUserSettings: function(username,settings) {
|
||||||
|
var current = userSettings[username];
|
||||||
|
userSettings[username] = settings;
|
||||||
|
try {
|
||||||
|
assert.deepEqual(current,settings);
|
||||||
|
return when.resolve();
|
||||||
|
} catch(err) {
|
||||||
|
globalSettings.users = userSettings;
|
||||||
|
return storage.saveSettings(globalSettings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,19 @@ var storageModuleInterface = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return when.reject(e);
|
return when.reject(e);
|
||||||
}
|
}
|
||||||
return storageModule.init(runtime.settings);
|
if (!!storageModule.projects) {
|
||||||
|
var projectsEnabled = false;
|
||||||
|
if (runtime.settings.hasOwnProperty("editorTheme") && runtime.settings.editorTheme.hasOwnProperty("projects")) {
|
||||||
|
projectsEnabled = runtime.settings.editorTheme.projects.enabled === true;
|
||||||
|
}
|
||||||
|
if (projectsEnabled) {
|
||||||
|
storageModuleInterface.projects = storageModule.projects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (storageModule.sshkeys) {
|
||||||
|
storageModuleInterface.sshkeys = storageModule.sshkeys;
|
||||||
|
}
|
||||||
|
return storageModule.init(runtime.settings,runtime);
|
||||||
},
|
},
|
||||||
getFlows: function() {
|
getFlows: function() {
|
||||||
return storageModule.getFlows().then(function(flows) {
|
return storageModule.getFlows().then(function(flows) {
|
||||||
@ -63,7 +75,7 @@ var storageModuleInterface = {
|
|||||||
flows: flows,
|
flows: flows,
|
||||||
credentials: creds
|
credentials: creds
|
||||||
};
|
};
|
||||||
result.rev = crypto.createHash('md5').update(JSON.stringify(result)).digest("hex");
|
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex");
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -81,16 +93,16 @@ var storageModuleInterface = {
|
|||||||
|
|
||||||
return credentialSavePromise.then(function() {
|
return credentialSavePromise.then(function() {
|
||||||
return storageModule.saveFlows(flows).then(function() {
|
return storageModule.saveFlows(flows).then(function() {
|
||||||
return crypto.createHash('md5').update(JSON.stringify(config)).digest("hex");
|
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex");
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// getCredentials: function() {
|
// getCredentials: function() {
|
||||||
// return storageModule.getCredentials();
|
// return storageModule.getCredentials();
|
||||||
// },
|
// },
|
||||||
// saveCredentials: function(credentials) {
|
saveCredentials: function(credentials) {
|
||||||
// return storageModule.saveCredentials(credentials);
|
return storageModule.saveCredentials(credentials);
|
||||||
// },
|
},
|
||||||
getSettings: function() {
|
getSettings: function() {
|
||||||
if (settingsAvailable) {
|
if (settingsAvailable) {
|
||||||
return storageModule.getSettings();
|
return storageModule.getSettings();
|
||||||
|
@ -1,448 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
var fs = require('fs-extra');
|
|
||||||
var when = require('when');
|
|
||||||
var nodeFn = require('when/node/function');
|
|
||||||
var keys = require('when/keys');
|
|
||||||
var fspath = require("path");
|
|
||||||
var mkdirp = fs.mkdirs;
|
|
||||||
|
|
||||||
var log = require("../log");
|
|
||||||
|
|
||||||
var promiseDir = nodeFn.lift(mkdirp);
|
|
||||||
|
|
||||||
var initialFlowLoadComplete = false;
|
|
||||||
var settings;
|
|
||||||
var flowsFile;
|
|
||||||
var flowsFullPath;
|
|
||||||
var flowsFileBackup;
|
|
||||||
var credentialsFile;
|
|
||||||
var credentialsFileBackup;
|
|
||||||
var oldCredentialsFile;
|
|
||||||
var sessionsFile;
|
|
||||||
var libDir;
|
|
||||||
var libFlowsDir;
|
|
||||||
var globalSettingsFile;
|
|
||||||
|
|
||||||
function getFileMeta(root,path) {
|
|
||||||
var fn = fspath.join(root,path);
|
|
||||||
var fd = fs.openSync(fn,"r");
|
|
||||||
var size = fs.fstatSync(fd).size;
|
|
||||||
var meta = {};
|
|
||||||
var read = 0;
|
|
||||||
var length = 10;
|
|
||||||
var remaining = Buffer(0);
|
|
||||||
var buffer = Buffer(length);
|
|
||||||
var idx = -1;
|
|
||||||
while(read < size) {
|
|
||||||
read+=fs.readSync(fd,buffer,0,length);
|
|
||||||
var data = Buffer.concat([remaining,buffer]);
|
|
||||||
while((idx = data.indexOf("\n")) != -1){
|
|
||||||
var part = data.slice(0,idx+1);
|
|
||||||
var match = /^\/\/ (\w+): (.*)/.exec(part.toString());
|
|
||||||
if (match) {
|
|
||||||
meta[match[1]] = match[2];
|
|
||||||
} else {
|
|
||||||
read = size;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
data = data.slice(idx+1);
|
|
||||||
}
|
|
||||||
remaining = data;
|
|
||||||
}
|
|
||||||
fs.closeSync(fd);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileBody(root,path) {
|
|
||||||
var body = Buffer(0);
|
|
||||||
var fn = fspath.join(root,path);
|
|
||||||
var fd = fs.openSync(fn,"r");
|
|
||||||
var size = fs.fstatSync(fd).size;
|
|
||||||
var scanning = true;
|
|
||||||
var read = 0;
|
|
||||||
var length = 50;
|
|
||||||
var remaining = Buffer(0);
|
|
||||||
var buffer = Buffer(length);
|
|
||||||
var idx = -1;
|
|
||||||
while(read < size) {
|
|
||||||
var thisRead = fs.readSync(fd,buffer,0,length);
|
|
||||||
read += thisRead;
|
|
||||||
if (scanning) {
|
|
||||||
var data = Buffer.concat([remaining,buffer.slice(0,thisRead)]);
|
|
||||||
while((idx = data.indexOf("\n")) != -1){
|
|
||||||
var part = data.slice(0,idx+1);
|
|
||||||
if (! /^\/\/ \w+: /.test(part.toString())) {
|
|
||||||
scanning = false;
|
|
||||||
body = Buffer.concat([body,data]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
data = data.slice(idx+1);
|
|
||||||
}
|
|
||||||
remaining = data;
|
|
||||||
if (scanning && read >= size) {
|
|
||||||
body = Buffer.concat([body,remaining]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body = Buffer.concat([body,buffer.slice(0,thisRead)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.closeSync(fd);
|
|
||||||
return body.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write content to a file using UTF8 encoding.
|
|
||||||
* This forces a fsync before completing to ensure
|
|
||||||
* the write hits disk.
|
|
||||||
*/
|
|
||||||
function writeFile(path,content) {
|
|
||||||
return when.promise(function(resolve,reject) {
|
|
||||||
var stream = fs.createWriteStream(path);
|
|
||||||
stream.on('open',function(fd) {
|
|
||||||
stream.write(content,'utf8',function() {
|
|
||||||
fs.fsync(fd,function(err) {
|
|
||||||
if (err) {
|
|
||||||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()}));
|
|
||||||
}
|
|
||||||
stream.end(resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
stream.on('error',function(err) {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseJSON(data) {
|
|
||||||
if (data.charCodeAt(0) === 0xFEFF) {
|
|
||||||
data = data.slice(1)
|
|
||||||
}
|
|
||||||
return JSON.parse(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readFile(path,backupPath,emptyResponse,type) {
|
|
||||||
return when.promise(function(resolve) {
|
|
||||||
fs.readFile(path,'utf8',function(err,data) {
|
|
||||||
if (!err) {
|
|
||||||
if (data.length === 0) {
|
|
||||||
log.warn(log._("storage.localfilesystem.empty",{type:type}));
|
|
||||||
try {
|
|
||||||
var backupStat = fs.statSync(backupPath);
|
|
||||||
if (backupStat.size === 0) {
|
|
||||||
// Empty flows, empty backup - return empty flow
|
|
||||||
return resolve(emptyResponse);
|
|
||||||
}
|
|
||||||
// Empty flows, restore backup
|
|
||||||
log.warn(log._("storage.localfilesystem.restore",{path:backupPath,type:type}));
|
|
||||||
fs.copy(backupPath,path,function(backupCopyErr) {
|
|
||||||
if (backupCopyErr) {
|
|
||||||
// Restore backup failed
|
|
||||||
log.warn(log._("storage.localfilesystem.restore-fail",{message:backupCopyErr.toString(),type:type}));
|
|
||||||
resolve([]);
|
|
||||||
} else {
|
|
||||||
// Loop back in to load the restored backup
|
|
||||||
resolve(readFile(path,backupPath,emptyResponse,type));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} catch(backupStatErr) {
|
|
||||||
// Empty flow file, no back-up file
|
|
||||||
return resolve(emptyResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return resolve(parseJSON(data));
|
|
||||||
} catch(parseErr) {
|
|
||||||
log.warn(log._("storage.localfilesystem.invalid",{type:type}));
|
|
||||||
return resolve(emptyResponse);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (type === 'flow') {
|
|
||||||
log.info(log._("storage.localfilesystem.create",{type:type}));
|
|
||||||
}
|
|
||||||
resolve(emptyResponse);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var localfilesystem = {
|
|
||||||
init: function(_settings) {
|
|
||||||
settings = _settings;
|
|
||||||
|
|
||||||
var promises = [];
|
|
||||||
|
|
||||||
if (!settings.userDir) {
|
|
||||||
try {
|
|
||||||
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json"));
|
|
||||||
settings.userDir = process.env.NODE_RED_HOME;
|
|
||||||
} catch(err) {
|
|
||||||
try {
|
|
||||||
// Consider compatibility for older versions
|
|
||||||
if (process.env.HOMEPATH) {
|
|
||||||
fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json"));
|
|
||||||
settings.userDir = fspath.join(process.env.HOMEPATH,".node-red");
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
}
|
|
||||||
if (!settings.userDir) {
|
|
||||||
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
|
||||||
if (!settings.readOnly) {
|
|
||||||
promises.push(promiseDir(fspath.join(settings.userDir,"node_modules")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.flowFile) {
|
|
||||||
flowsFile = settings.flowFile;
|
|
||||||
// handle Unix and Windows "C:\"
|
|
||||||
if ((flowsFile[0] == "/") || (flowsFile[1] == ":")) {
|
|
||||||
// Absolute path
|
|
||||||
flowsFullPath = flowsFile;
|
|
||||||
} else if (flowsFile.substring(0,2) === "./") {
|
|
||||||
// Relative to cwd
|
|
||||||
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
fs.statSync(fspath.join(process.cwd(),flowsFile));
|
|
||||||
// Found in cwd
|
|
||||||
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
|
||||||
} catch(err) {
|
|
||||||
// Use userDir
|
|
||||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
flowsFile = 'flows_'+require('os').hostname()+'.json';
|
|
||||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
|
||||||
}
|
|
||||||
var ffExt = fspath.extname(flowsFullPath);
|
|
||||||
var ffName = fspath.basename(flowsFullPath);
|
|
||||||
var ffBase = fspath.basename(flowsFullPath,ffExt);
|
|
||||||
var ffDir = fspath.dirname(flowsFullPath);
|
|
||||||
|
|
||||||
credentialsFile = fspath.join(settings.userDir,ffBase+"_cred"+ffExt);
|
|
||||||
credentialsFileBackup = fspath.join(settings.userDir,"."+ffBase+"_cred"+ffExt+".backup");
|
|
||||||
|
|
||||||
oldCredentialsFile = fspath.join(settings.userDir,"credentials.json");
|
|
||||||
|
|
||||||
flowsFileBackup = fspath.join(ffDir,"."+ffName+".backup");
|
|
||||||
|
|
||||||
sessionsFile = fspath.join(settings.userDir,".sessions.json");
|
|
||||||
|
|
||||||
libDir = fspath.join(settings.userDir,"lib");
|
|
||||||
libFlowsDir = fspath.join(libDir,"flows");
|
|
||||||
|
|
||||||
globalSettingsFile = fspath.join(settings.userDir,".config.json");
|
|
||||||
|
|
||||||
var packageFile = fspath.join(settings.userDir,"package.json");
|
|
||||||
var packagePromise = when.resolve();
|
|
||||||
if (!settings.readOnly) {
|
|
||||||
promises.push(promiseDir(libFlowsDir));
|
|
||||||
packagePromise = function() {
|
|
||||||
try {
|
|
||||||
fs.statSync(packageFile);
|
|
||||||
} catch(err) {
|
|
||||||
var defaultPackage = {
|
|
||||||
"name": "node-red-project",
|
|
||||||
"description": "A Node-RED Project",
|
|
||||||
"version": "0.0.1"
|
|
||||||
};
|
|
||||||
return writeFile(packageFile,JSON.stringify(defaultPackage,"",4));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return when.all(promises).then(packagePromise);
|
|
||||||
},
|
|
||||||
|
|
||||||
getFlows: function() {
|
|
||||||
if (!initialFlowLoadComplete) {
|
|
||||||
initialFlowLoadComplete = true;
|
|
||||||
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
|
||||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
|
||||||
}
|
|
||||||
return readFile(flowsFullPath,flowsFileBackup,[],'flow');
|
|
||||||
},
|
|
||||||
|
|
||||||
saveFlows: function(flows) {
|
|
||||||
if (settings.readOnly) {
|
|
||||||
return when.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.renameSync(flowsFullPath,flowsFileBackup);
|
|
||||||
} catch(err) {
|
|
||||||
}
|
|
||||||
|
|
||||||
var flowData;
|
|
||||||
|
|
||||||
if (settings.flowFilePretty) {
|
|
||||||
flowData = JSON.stringify(flows,null,4);
|
|
||||||
} else {
|
|
||||||
flowData = JSON.stringify(flows);
|
|
||||||
}
|
|
||||||
return writeFile(flowsFullPath, flowData);
|
|
||||||
},
|
|
||||||
|
|
||||||
getCredentials: function() {
|
|
||||||
return readFile(credentialsFile,credentialsFileBackup,{},'credentials');
|
|
||||||
},
|
|
||||||
|
|
||||||
saveCredentials: function(credentials) {
|
|
||||||
if (settings.readOnly) {
|
|
||||||
return when.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.renameSync(credentialsFile,credentialsFileBackup);
|
|
||||||
} catch(err) {
|
|
||||||
}
|
|
||||||
var credentialData;
|
|
||||||
if (settings.flowFilePretty) {
|
|
||||||
credentialData = JSON.stringify(credentials,null,4);
|
|
||||||
} else {
|
|
||||||
credentialData = JSON.stringify(credentials);
|
|
||||||
}
|
|
||||||
return writeFile(credentialsFile, credentialData);
|
|
||||||
},
|
|
||||||
|
|
||||||
getSettings: function() {
|
|
||||||
return when.promise(function(resolve,reject) {
|
|
||||||
fs.readFile(globalSettingsFile,'utf8',function(err,data) {
|
|
||||||
if (!err) {
|
|
||||||
try {
|
|
||||||
return resolve(parseJSON(data));
|
|
||||||
} catch(err2) {
|
|
||||||
log.trace("Corrupted config detected - resetting");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolve({});
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
saveSettings: function(newSettings) {
|
|
||||||
if (settings.readOnly) {
|
|
||||||
return when.resolve();
|
|
||||||
}
|
|
||||||
return writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1));
|
|
||||||
},
|
|
||||||
getSessions: function() {
|
|
||||||
return when.promise(function(resolve,reject) {
|
|
||||||
fs.readFile(sessionsFile,'utf8',function(err,data){
|
|
||||||
if (!err) {
|
|
||||||
try {
|
|
||||||
return resolve(parseJSON(data));
|
|
||||||
} catch(err2) {
|
|
||||||
log.trace("Corrupted sessions file - resetting");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve({});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
saveSessions: function(sessions) {
|
|
||||||
if (settings.readOnly) {
|
|
||||||
return when.resolve();
|
|
||||||
}
|
|
||||||
return writeFile(sessionsFile,JSON.stringify(sessions));
|
|
||||||
},
|
|
||||||
|
|
||||||
getLibraryEntry: function(type,path) {
|
|
||||||
var root = fspath.join(libDir,type);
|
|
||||||
var rootPath = fspath.join(libDir,type,path);
|
|
||||||
|
|
||||||
// don't create the folder if it does not exist - we are only reading....
|
|
||||||
return nodeFn.call(fs.lstat, rootPath).then(function(stats) {
|
|
||||||
if (stats.isFile()) {
|
|
||||||
return getFileBody(root,path);
|
|
||||||
}
|
|
||||||
if (path.substr(-1) == '/') {
|
|
||||||
path = path.substr(0,path.length-1);
|
|
||||||
}
|
|
||||||
return nodeFn.call(fs.readdir, rootPath).then(function(fns) {
|
|
||||||
var dirs = [];
|
|
||||||
var files = [];
|
|
||||||
fns.sort().filter(function(fn) {
|
|
||||||
var fullPath = fspath.join(path,fn);
|
|
||||||
var absoluteFullPath = fspath.join(root,fullPath);
|
|
||||||
if (fn[0] != ".") {
|
|
||||||
var stats = fs.lstatSync(absoluteFullPath);
|
|
||||||
if (stats.isDirectory()) {
|
|
||||||
dirs.push(fn);
|
|
||||||
} else {
|
|
||||||
var meta = getFileMeta(root,fullPath);
|
|
||||||
meta.fn = fn;
|
|
||||||
files.push(meta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return dirs.concat(files);
|
|
||||||
});
|
|
||||||
}).otherwise(function(err) {
|
|
||||||
// if path is empty, then assume it was a folder, return empty
|
|
||||||
if (path === ""){
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// if path ends with slash, it was a folder
|
|
||||||
// so return empty
|
|
||||||
if (path.substr(-1) == '/') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// else path was specified, but did not exist,
|
|
||||||
// check for path.json as an alternative if flows
|
|
||||||
if (type === "flows" && !/\.json$/.test(path)) {
|
|
||||||
return localfilesystem.getLibraryEntry(type,path+".json")
|
|
||||||
.otherwise(function(e) {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
saveLibraryEntry: function(type,path,meta,body) {
|
|
||||||
if (settings.readOnly) {
|
|
||||||
return when.resolve();
|
|
||||||
}
|
|
||||||
if (type === "flows" && !path.endsWith(".json")) {
|
|
||||||
path += ".json";
|
|
||||||
}
|
|
||||||
var fn = fspath.join(libDir, type, path);
|
|
||||||
var headers = "";
|
|
||||||
for (var i in meta) {
|
|
||||||
if (meta.hasOwnProperty(i)) {
|
|
||||||
headers += "// "+i+": "+meta[i]+"\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === "flows" && settings.flowFilePretty) {
|
|
||||||
body = JSON.stringify(JSON.parse(body),null,4);
|
|
||||||
}
|
|
||||||
return promiseDir(fspath.dirname(fn)).then(function () {
|
|
||||||
writeFile(fn,headers+body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = localfilesystem;
|
|
100
red/runtime/storage/localfilesystem/index.js
Normal file
100
red/runtime/storage/localfilesystem/index.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var when = require('when');
|
||||||
|
var fspath = require("path");
|
||||||
|
|
||||||
|
var log = require("../../log");
|
||||||
|
var util = require("./util");
|
||||||
|
var library = require("./library");
|
||||||
|
var sessions = require("./sessions");
|
||||||
|
var runtimeSettings = require("./settings");
|
||||||
|
var projects = require("./projects");
|
||||||
|
|
||||||
|
var initialFlowLoadComplete = false;
|
||||||
|
var settings;
|
||||||
|
|
||||||
|
var localfilesystem = {
|
||||||
|
init: function(_settings, runtime) {
|
||||||
|
settings = _settings;
|
||||||
|
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
if (!settings.userDir) {
|
||||||
|
try {
|
||||||
|
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json"));
|
||||||
|
settings.userDir = process.env.NODE_RED_HOME;
|
||||||
|
} catch(err) {
|
||||||
|
try {
|
||||||
|
// Consider compatibility for older versions
|
||||||
|
if (process.env.HOMEPATH) {
|
||||||
|
fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json"));
|
||||||
|
settings.userDir = fspath.join(process.env.HOMEPATH,".node-red");
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
}
|
||||||
|
if (!settings.userDir) {
|
||||||
|
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
||||||
|
if (!settings.readOnly) {
|
||||||
|
promises.push(fs.ensureDir(fspath.join(settings.userDir,"node_modules")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions.init(settings);
|
||||||
|
runtimeSettings.init(settings);
|
||||||
|
promises.push(library.init(settings));
|
||||||
|
promises.push(projects.init(settings, runtime));
|
||||||
|
|
||||||
|
var packageFile = fspath.join(settings.userDir,"package.json");
|
||||||
|
var packagePromise = when.resolve();
|
||||||
|
|
||||||
|
if (!settings.readOnly) {
|
||||||
|
packagePromise = function() {
|
||||||
|
try {
|
||||||
|
fs.statSync(packageFile);
|
||||||
|
} catch(err) {
|
||||||
|
var defaultPackage = {
|
||||||
|
"name": "node-red-project",
|
||||||
|
"description": "A Node-RED Project",
|
||||||
|
"version": "0.0.1"
|
||||||
|
};
|
||||||
|
return util.writeFile(packageFile,JSON.stringify(defaultPackage,"",4));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return when.all(promises).then(packagePromise);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getFlows: projects.getFlows,
|
||||||
|
saveFlows: projects.saveFlows,
|
||||||
|
getCredentials: projects.getCredentials,
|
||||||
|
saveCredentials: projects.saveCredentials,
|
||||||
|
|
||||||
|
getSettings: runtimeSettings.getSettings,
|
||||||
|
saveSettings: runtimeSettings.saveSettings,
|
||||||
|
getSessions: sessions.getSessions,
|
||||||
|
saveSessions: sessions.saveSessions,
|
||||||
|
getLibraryEntry: library.getLibraryEntry,
|
||||||
|
saveLibraryEntry: library.saveLibraryEntry,
|
||||||
|
projects: projects
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = localfilesystem;
|
183
red/runtime/storage/localfilesystem/library.js
Normal file
183
red/runtime/storage/localfilesystem/library.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var when = require('when');
|
||||||
|
var fspath = require("path");
|
||||||
|
var nodeFn = require('when/node/function');
|
||||||
|
|
||||||
|
var util = require("./util");
|
||||||
|
|
||||||
|
var settings;
|
||||||
|
var libDir;
|
||||||
|
var libFlowsDir;
|
||||||
|
|
||||||
|
|
||||||
|
function getFileMeta(root,path) {
|
||||||
|
var fn = fspath.join(root,path);
|
||||||
|
var fd = fs.openSync(fn,"r");
|
||||||
|
var size = fs.fstatSync(fd).size;
|
||||||
|
var meta = {};
|
||||||
|
var read = 0;
|
||||||
|
var length = 10;
|
||||||
|
var remaining = "";
|
||||||
|
var buffer = Buffer(length);
|
||||||
|
while(read < size) {
|
||||||
|
read+=fs.readSync(fd,buffer,0,length);
|
||||||
|
var data = remaining+buffer.toString();
|
||||||
|
var parts = data.split("\n");
|
||||||
|
remaining = parts.splice(-1);
|
||||||
|
for (var i=0;i<parts.length;i+=1) {
|
||||||
|
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
|
||||||
|
if (match) {
|
||||||
|
meta[match[1]] = match[2];
|
||||||
|
} else {
|
||||||
|
read = size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.closeSync(fd);
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileBody(root,path) {
|
||||||
|
var body = "";
|
||||||
|
var fn = fspath.join(root,path);
|
||||||
|
var fd = fs.openSync(fn,"r");
|
||||||
|
var size = fs.fstatSync(fd).size;
|
||||||
|
var scanning = true;
|
||||||
|
var read = 0;
|
||||||
|
var length = 50;
|
||||||
|
var remaining = "";
|
||||||
|
var buffer = Buffer(length);
|
||||||
|
while(read < size) {
|
||||||
|
var thisRead = fs.readSync(fd,buffer,0,length);
|
||||||
|
read += thisRead;
|
||||||
|
if (scanning) {
|
||||||
|
var data = remaining+buffer.slice(0,thisRead).toString();
|
||||||
|
var parts = data.split("\n");
|
||||||
|
remaining = parts.splice(-1)[0];
|
||||||
|
for (var i=0;i<parts.length;i+=1) {
|
||||||
|
if (! /^\/\/ \w+: /.test(parts[i])) {
|
||||||
|
scanning = false;
|
||||||
|
body += parts[i]+"\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! /^\/\/ \w+: /.test(remaining)) {
|
||||||
|
scanning = false;
|
||||||
|
}
|
||||||
|
if (!scanning) {
|
||||||
|
body += remaining;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body += buffer.slice(0,thisRead).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.closeSync(fd);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
function getLibraryEntry(type,path) {
|
||||||
|
var root = fspath.join(libDir,type);
|
||||||
|
var rootPath = fspath.join(libDir,type,path);
|
||||||
|
|
||||||
|
// don't create the folder if it does not exist - we are only reading....
|
||||||
|
return nodeFn.call(fs.lstat, rootPath).then(function(stats) {
|
||||||
|
if (stats.isFile()) {
|
||||||
|
return getFileBody(root,path);
|
||||||
|
}
|
||||||
|
if (path.substr(-1) == '/') {
|
||||||
|
path = path.substr(0,path.length-1);
|
||||||
|
}
|
||||||
|
return nodeFn.call(fs.readdir, rootPath).then(function(fns) {
|
||||||
|
var dirs = [];
|
||||||
|
var files = [];
|
||||||
|
fns.sort().filter(function(fn) {
|
||||||
|
var fullPath = fspath.join(path,fn);
|
||||||
|
var absoluteFullPath = fspath.join(root,fullPath);
|
||||||
|
if (fn[0] != ".") {
|
||||||
|
var stats = fs.lstatSync(absoluteFullPath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
dirs.push(fn);
|
||||||
|
} else {
|
||||||
|
var meta = getFileMeta(root,fullPath);
|
||||||
|
meta.fn = fn;
|
||||||
|
files.push(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dirs.concat(files);
|
||||||
|
});
|
||||||
|
}).catch(function(err) {
|
||||||
|
// if path is empty, then assume it was a folder, return empty
|
||||||
|
if (path === ""){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if path ends with slash, it was a folder
|
||||||
|
// so return empty
|
||||||
|
if (path.substr(-1) == '/') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// else path was specified, but did not exist,
|
||||||
|
// check for path.json as an alternative if flows
|
||||||
|
if (type === "flows" && !/\.json$/.test(path)) {
|
||||||
|
return getLibraryEntry(type,path+".json")
|
||||||
|
.catch(function(e) {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_settings) {
|
||||||
|
settings = _settings;
|
||||||
|
libDir = fspath.join(settings.userDir,"lib");
|
||||||
|
libFlowsDir = fspath.join(libDir,"flows");
|
||||||
|
if (!settings.readOnly) {
|
||||||
|
return fs.ensureDir(libFlowsDir);
|
||||||
|
} else {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getLibraryEntry: getLibraryEntry,
|
||||||
|
|
||||||
|
saveLibraryEntry: function(type,path,meta,body) {
|
||||||
|
if (settings.readOnly) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
if (type === "flows" && !path.endsWith(".json")) {
|
||||||
|
path += ".json";
|
||||||
|
}
|
||||||
|
var fn = fspath.join(libDir, type, path);
|
||||||
|
var headers = "";
|
||||||
|
for (var i in meta) {
|
||||||
|
if (meta.hasOwnProperty(i)) {
|
||||||
|
headers += "// "+i+": "+meta[i]+"\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === "flows" && settings.flowFilePretty) {
|
||||||
|
body = JSON.stringify(JSON.parse(body),null,4);
|
||||||
|
}
|
||||||
|
return fs.ensureDir(fspath.dirname(fn)).then(function () {
|
||||||
|
util.writeFile(fn,headers+body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
1001
red/runtime/storage/localfilesystem/projects/Project.js
Normal file
1001
red/runtime/storage/localfilesystem/projects/Project.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"package.json": function(project) {
|
||||||
|
var package = {
|
||||||
|
"name": project.name,
|
||||||
|
"description": project.summary||"A Node-RED Project",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {},
|
||||||
|
"node-red": {
|
||||||
|
"settings": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (project.files) {
|
||||||
|
if (project.files.flow) {
|
||||||
|
package['node-red'].settings.flowFile = project.files.flow;
|
||||||
|
package['node-red'].settings.credentialsFile = project.files.credentials;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSON.stringify(package,"",4);
|
||||||
|
},
|
||||||
|
"README.md": function(project) {
|
||||||
|
return project.name+"\n"+("=".repeat(project.name.length))+"\n\n"+(project.summary||"A Node-RED Project")+"\n\n";
|
||||||
|
},
|
||||||
|
"settings.json": function() { return "{}" },
|
||||||
|
".gitignore": function() { return "*.backup" ;}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var authCache = {}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function() {
|
||||||
|
authCache = {};
|
||||||
|
},
|
||||||
|
clear: function(project,remote, user) {
|
||||||
|
if (user && remote && authCache[project] && authCache[project][remote]) {
|
||||||
|
delete authCache[project][remote][user];
|
||||||
|
} else if (remote && authCache.hasOwnProperty(project)) {
|
||||||
|
delete authCache[project][remote];
|
||||||
|
} else {
|
||||||
|
delete authCache[project];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: function(project,remote,user,auth) {
|
||||||
|
// console.log("AuthCache.set",remote,user,auth);
|
||||||
|
authCache[project] = authCache[project]||{};
|
||||||
|
authCache[project][remote] = authCache[project][remote]||{};
|
||||||
|
authCache[project][remote][user] = auth;
|
||||||
|
// console.log(JSON.stringify(authCache,'',4));
|
||||||
|
},
|
||||||
|
get: function(project,remote,user) {
|
||||||
|
// console.log("AuthCache.get",remote,user,authCache[project]&&authCache[project][remote]&&authCache[project][remote][user]);
|
||||||
|
if (authCache[project] && authCache[project][remote]) {
|
||||||
|
return authCache[project][remote][user];
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
132
red/runtime/storage/localfilesystem/projects/git/authServer.js
Normal file
132
red/runtime/storage/localfilesystem/projects/git/authServer.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var net = require("net");
|
||||||
|
var fs = require("fs-extra");
|
||||||
|
var path = require("path");
|
||||||
|
var os = require("os");
|
||||||
|
|
||||||
|
function getListenPath() {
|
||||||
|
var seed = (0x100000+Math.random()*0x999999).toString(16);
|
||||||
|
var fn = 'node-red-git-askpass-'+seed+'-sock';
|
||||||
|
var listenPath;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
listenPath = '\\\\.\\pipe\\'+getListenPath;
|
||||||
|
} else {
|
||||||
|
listenPath = path.join(process.env['XDG_RUNTIME_DIR'] || os.tmpdir(), fn);
|
||||||
|
}
|
||||||
|
// console.log(listenPath);
|
||||||
|
return listenPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var ResponseServer = function(auth) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var server = net.createServer(function(connection) {
|
||||||
|
connection.setEncoding('utf8');
|
||||||
|
var parts = [];
|
||||||
|
connection.on('data', function(data) {
|
||||||
|
var m = data.indexOf("\n");
|
||||||
|
if (m !== -1) {
|
||||||
|
parts.push(data.substring(0, m));
|
||||||
|
data = data.substring(m);
|
||||||
|
var line = parts.join("");
|
||||||
|
// console.log("LINE:",line);
|
||||||
|
parts = [];
|
||||||
|
if (line==='Username') {
|
||||||
|
connection.end(auth.username);
|
||||||
|
} else if (line === 'Password') {
|
||||||
|
connection.end(auth.password);
|
||||||
|
server.close();
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.length > 0) {
|
||||||
|
parts.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var listenPath = getListenPath();
|
||||||
|
|
||||||
|
server.listen(listenPath, function(ready) {
|
||||||
|
resolve({path:listenPath,close:function() { server.close(); }});
|
||||||
|
});
|
||||||
|
server.on('close', function() {
|
||||||
|
// console.log("Closing response server");
|
||||||
|
fs.removeSync(listenPath);
|
||||||
|
});
|
||||||
|
server.on('error',function(err) {
|
||||||
|
console.log("ResponseServer unexpectedError:",err.toString());
|
||||||
|
server.close();
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var ResponseSSHServer = function(auth) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var server = net.createServer(function(connection) {
|
||||||
|
connection.setEncoding('utf8');
|
||||||
|
var parts = [];
|
||||||
|
connection.on('data', function(data) {
|
||||||
|
var m = data.indexOf("\n");
|
||||||
|
if (m !== -1) {
|
||||||
|
parts.push(data.substring(0, m));
|
||||||
|
data = data.substring(m);
|
||||||
|
var line = parts.join("");
|
||||||
|
parts = [];
|
||||||
|
if (line==='The') {
|
||||||
|
// TODO: document these exchanges!
|
||||||
|
connection.end('yes');
|
||||||
|
// server.close();
|
||||||
|
} else if (line === 'Enter') {
|
||||||
|
connection.end(auth.passphrase);
|
||||||
|
// server.close();
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.length > 0) {
|
||||||
|
parts.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var listenPath = getListenPath();
|
||||||
|
|
||||||
|
server.listen(listenPath, function(ready) {
|
||||||
|
resolve({path:listenPath,close:function() { server.close(); }});
|
||||||
|
});
|
||||||
|
server.on('close', function() {
|
||||||
|
// console.log("Closing response server");
|
||||||
|
fs.removeSync(listenPath);
|
||||||
|
});
|
||||||
|
server.on('error',function(err) {
|
||||||
|
console.log("ResponseServer unexpectedError:",err.toString());
|
||||||
|
server.close();
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ResponseServer: ResponseServer,
|
||||||
|
ResponseSSHServer: ResponseSSHServer
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var net = require("net");
|
||||||
|
var socket = net.connect(process.argv[2], function() {
|
||||||
|
socket.on('data', function(data) { console.log(data);});
|
||||||
|
socket.on('end', function() {
|
||||||
|
});
|
||||||
|
socket.write((process.argv[3]||"")+"\n", 'utf8');
|
||||||
|
});
|
||||||
|
socket.setEncoding('utf8');
|
621
red/runtime/storage/localfilesystem/projects/git/index.js
Normal file
621
red/runtime/storage/localfilesystem/projects/git/index.js
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var when = require('when');
|
||||||
|
var exec = require('child_process').exec;
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
var authResponseServer = require('./authServer').ResponseServer;
|
||||||
|
var sshResponseServer = require('./authServer').ResponseSSHServer;
|
||||||
|
var clone = require('clone');
|
||||||
|
var path = require("path");
|
||||||
|
|
||||||
|
var gitCommand = "git";
|
||||||
|
var gitVersion;
|
||||||
|
var log;
|
||||||
|
|
||||||
|
function runGitCommand(args,cwd,env) {
|
||||||
|
log.trace(gitCommand + JSON.stringify(args));
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
args.unshift("credential.helper=")
|
||||||
|
args.unshift("-c");
|
||||||
|
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env});
|
||||||
|
var stdout = "";
|
||||||
|
var stderr = "";
|
||||||
|
child.stdout.on('data', function(data) {
|
||||||
|
stdout += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', function(data) {
|
||||||
|
stderr += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', function(code) {
|
||||||
|
if (code !== 0) {
|
||||||
|
var err = new Error(stderr);
|
||||||
|
err.stdout = stdout;
|
||||||
|
err.stderr = stderr;
|
||||||
|
if (/fatal: could not read Username/.test(stderr)) {
|
||||||
|
err.code = "git_auth_failed";
|
||||||
|
} else if(/HTTP Basic: Access denied/.test(stderr)) {
|
||||||
|
err.code = "git_auth_failed";
|
||||||
|
} else if(/Permission denied \(publickey\)/.test(stderr)) {
|
||||||
|
err.code = "git_auth_failed";
|
||||||
|
} else if(/Connection refused/.test(stderr)) {
|
||||||
|
err.code = "git_connection_failed";
|
||||||
|
} else if (/commit your changes or stash/.test(stderr)) {
|
||||||
|
err.code = "git_local_overwrite";
|
||||||
|
} else if (/CONFLICT/.test(err.stdout)) {
|
||||||
|
err.code = "git_pull_merge_conflict";
|
||||||
|
} else if (/not fully merged/.test(stderr)) {
|
||||||
|
err.code = "git_delete_branch_unmerged";
|
||||||
|
} else if (/remote .* already exists/.test(stderr)) {
|
||||||
|
err.code = "git_remote_already_exists";
|
||||||
|
}
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(stdout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function runGitCommandWithAuth(args,cwd,auth) {
|
||||||
|
return authResponseServer(auth).then(function(rs) {
|
||||||
|
var commandEnv = clone(process.env);
|
||||||
|
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
|
||||||
|
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
|
||||||
|
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
|
||||||
|
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
|
||||||
|
return runGitCommand(args,cwd,commandEnv).finally(function() {
|
||||||
|
rs.close();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function runGitCommandWithSSHCommand(args,cwd,auth) {
|
||||||
|
return sshResponseServer(auth).then(function(rs) {
|
||||||
|
var commandEnv = clone(process.env);
|
||||||
|
commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
|
||||||
|
commandEnv.DISPLAY = "dummy:0";
|
||||||
|
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
|
||||||
|
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
|
||||||
|
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
|
||||||
|
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
|
||||||
|
// console.log('commandEnv:', commandEnv);
|
||||||
|
return runGitCommand(args,cwd,commandEnv).finally(function() {
|
||||||
|
rs.close();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanFilename(name) {
|
||||||
|
if (name[0] !== '"') {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return name.substring(1,name.length-1);
|
||||||
|
}
|
||||||
|
function parseFilenames(name) {
|
||||||
|
var re = /([^ "]+|(".*?"))($| -> ([^ ]+|(".*"))$)/;
|
||||||
|
var m = re.exec(name);
|
||||||
|
var result = [];
|
||||||
|
if (m) {
|
||||||
|
result.push(cleanFilename(m[1]));
|
||||||
|
if (m[4]) {
|
||||||
|
result.push(cleanFilename(m[4]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// function getBranchInfo(localRepo) {
|
||||||
|
// return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
|
||||||
|
// var lines = output.split("\n");
|
||||||
|
// var unknownDirs = [];
|
||||||
|
// var branchLineRE = /^## (No commits yet on )?(.+?)($|\.\.\.(.+?)($| \[(ahead (\d+))?.*?(behind (\d+))?\]))/m;
|
||||||
|
// console.log(output);
|
||||||
|
// console.log(lines);
|
||||||
|
// var m = branchLineRE.exec(output);
|
||||||
|
// console.log(m);
|
||||||
|
// var result = {}; //commits:{}};
|
||||||
|
// if (m) {
|
||||||
|
// if (m[1]) {
|
||||||
|
// result.empty = true;
|
||||||
|
// }
|
||||||
|
// result.local = m[2];
|
||||||
|
// if (m[4]) {
|
||||||
|
// result.remote = m[4];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
function getStatus(localRepo) {
|
||||||
|
// parseFilename('"test with space"');
|
||||||
|
// parseFilename('"test with space" -> knownFile.txt');
|
||||||
|
// parseFilename('"test with space" -> "un -> knownFile.txt"');
|
||||||
|
var result = {
|
||||||
|
files: {},
|
||||||
|
commits: {},
|
||||||
|
branches: {}
|
||||||
|
}
|
||||||
|
return runGitCommand(['rev-list', 'HEAD', '--count'],localRepo).then(function(count) {
|
||||||
|
result.commits.total = parseInt(count);
|
||||||
|
}).catch(function(err) {
|
||||||
|
if (/ambiguous argument/.test(err.message)) {
|
||||||
|
result.commits.total = 0;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}).then(function() {
|
||||||
|
return runGitCommand(["ls-files","--cached","--others","--exclude-standard"],localRepo).then(function(output) {
|
||||||
|
var lines = output.split("\n");
|
||||||
|
lines.forEach(function(l) {
|
||||||
|
if (l==="") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var fullName = cleanFilename(l);
|
||||||
|
// parseFilename(l);
|
||||||
|
var parts = fullName.split("/");
|
||||||
|
var p = result.files;
|
||||||
|
var name;
|
||||||
|
for (var i = 0;i<parts.length-1;i++) {
|
||||||
|
var name = parts.slice(0,i+1).join("/")+"/";
|
||||||
|
if (!p.hasOwnProperty(name)) {
|
||||||
|
p[name] = {
|
||||||
|
type:"d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.files[fullName] = {
|
||||||
|
type: /\/$/.test(fullName)?"d":"f"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
|
||||||
|
var lines = output.split("\n");
|
||||||
|
var unknownDirs = [];
|
||||||
|
var branchLineRE = /^## (?:No commits yet on )?(.+?)(?:$|\.\.\.(.+?)(?:$| \[(?:(?:ahead (\d+)(?:,\s*)?)?(?:behind (\d+))?|(gone))\]))/;
|
||||||
|
lines.forEach(function(line) {
|
||||||
|
if (line==="") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (line[0] === "#") {
|
||||||
|
var m = branchLineRE.exec(line);
|
||||||
|
if (m) {
|
||||||
|
result.branches.local = m[1];
|
||||||
|
if (m[2]) {
|
||||||
|
result.branches.remote = m[2];
|
||||||
|
result.commits.ahead = 0;
|
||||||
|
result.commits.behind = 0;
|
||||||
|
}
|
||||||
|
if (m[3] !== undefined) {
|
||||||
|
result.commits.ahead = parseInt(m[3]);
|
||||||
|
}
|
||||||
|
if (m[4] !== undefined) {
|
||||||
|
result.commits.behind = parseInt(m[4]);
|
||||||
|
}
|
||||||
|
if (m[5] !== undefined) {
|
||||||
|
result.commits.ahead = result.commits.total;
|
||||||
|
result.branches.remoteError = {
|
||||||
|
code: "git_remote_gone"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var status = line.substring(0,2);
|
||||||
|
var fileName;
|
||||||
|
var names;
|
||||||
|
if (status !== '??') {
|
||||||
|
names = parseFilenames(line.substring(3));
|
||||||
|
} else {
|
||||||
|
names = [cleanFilename(line.substring(3))];
|
||||||
|
}
|
||||||
|
fileName = names[0];
|
||||||
|
if (names.length > 1) {
|
||||||
|
fileName = names[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFilename(fileName);
|
||||||
|
if (fileName.charCodeAt(0) === 34) {
|
||||||
|
fileName = fileName.substring(1,fileName.length-1);
|
||||||
|
}
|
||||||
|
if (result.files.hasOwnProperty(fileName)) {
|
||||||
|
result.files[fileName].status = status;
|
||||||
|
} else {
|
||||||
|
result.files[fileName] = {
|
||||||
|
type: "f",
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (names.length > 1) {
|
||||||
|
result.files[fileName].oldName = names[0];
|
||||||
|
}
|
||||||
|
if (status === "??" && fileName[fileName.length-1] === '/') {
|
||||||
|
unknownDirs.push(fileName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var allFilenames = Object.keys(result.files);
|
||||||
|
allFilenames.forEach(function(f) {
|
||||||
|
var entry = result.files[f];
|
||||||
|
if (!entry.hasOwnProperty('status')) {
|
||||||
|
unknownDirs.forEach(function(uf) {
|
||||||
|
if (f.startsWith(uf)) {
|
||||||
|
entry.status = "??"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// console.log(files);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLog(log) {
|
||||||
|
var lines = log.split("\n");
|
||||||
|
var currentCommit = {};
|
||||||
|
var commits = [];
|
||||||
|
lines.forEach(function(l) {
|
||||||
|
if (l === "-----") {
|
||||||
|
commits.push(currentCommit);
|
||||||
|
currentCommit = {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var m = /^(.*): (.*)$/.exec(l);
|
||||||
|
if (m) {
|
||||||
|
if (m[1] === 'refs' && m[2]) {
|
||||||
|
currentCommit[m[1]] = m[2].split(",").map(function(v) { return v.trim() });
|
||||||
|
} else {
|
||||||
|
if (m[1] === 'parents') {
|
||||||
|
currentCommit[m[1]] = m[2].split(" ");
|
||||||
|
} else {
|
||||||
|
currentCommit[m[1]] = m[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return commits;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRemotes(cwd) {
|
||||||
|
return runGitCommand(['remote','-v'],cwd).then(function(output) {
|
||||||
|
var result;
|
||||||
|
if (output.length > 0) {
|
||||||
|
result = {};
|
||||||
|
var remoteRE = /^(.+)\t(.+) \((.+)\)$/gm;
|
||||||
|
var m;
|
||||||
|
while ((m = remoteRE.exec(output)) !== null) {
|
||||||
|
result[m[1]] = result[m[1]]||{};
|
||||||
|
result[m[1]][m[3]] = m[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBranches(cwd, remote) {
|
||||||
|
var args = ['branch','-vv','--no-color'];
|
||||||
|
if (remote) {
|
||||||
|
args.push('-r');
|
||||||
|
}
|
||||||
|
var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (?:ahead (\d+)(?:, )?)?(?:behind (\d+))?)?\])? (.*)$/;
|
||||||
|
return runGitCommand(args,cwd).then(function(output) {
|
||||||
|
var branches = [];
|
||||||
|
var lines = output.split("\n");
|
||||||
|
branches = lines.map(function(l) {
|
||||||
|
var m = branchRE.exec(l);
|
||||||
|
var branch = null;
|
||||||
|
if (m) {
|
||||||
|
branch = {
|
||||||
|
name: m[2],
|
||||||
|
remote: m[4],
|
||||||
|
status: {
|
||||||
|
ahead: m[5]||0,
|
||||||
|
behind: m[6]||0,
|
||||||
|
},
|
||||||
|
commit: {
|
||||||
|
sha: m[3],
|
||||||
|
subject: m[7]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m[1] === '* ') {
|
||||||
|
branch.current = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return branch;
|
||||||
|
}).filter(function(v) { return !!v && v.commit.sha !== '->' });
|
||||||
|
|
||||||
|
return {branches:branches};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getBranchStatus(cwd,remoteBranch) {
|
||||||
|
var commands = [
|
||||||
|
// #commits master ahead
|
||||||
|
runGitCommand(['rev-list', 'HEAD','^'+remoteBranch, '--count'],cwd),
|
||||||
|
// #commits master behind
|
||||||
|
runGitCommand(['rev-list', '^HEAD',remoteBranch, '--count'],cwd)
|
||||||
|
];
|
||||||
|
return when.all(commands).then(function(results) {
|
||||||
|
return {
|
||||||
|
commits: {
|
||||||
|
ahead: parseInt(results[0]),
|
||||||
|
behind: parseInt(results[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRemote(cwd,name,options) {
|
||||||
|
var args = ["remote","add",name,options.url]
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
}
|
||||||
|
function removeRemote(cwd,name) {
|
||||||
|
var args = ["remote","remove",name];
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_settings,_runtime) {
|
||||||
|
log = _runtime.log
|
||||||
|
return new Promise(function(resolve,reject) {
|
||||||
|
Promise.all([
|
||||||
|
runGitCommand(["--version"]),
|
||||||
|
runGitCommand(["config","--global","user.name"]).catch(err=>""),
|
||||||
|
runGitCommand(["config","--global","user.email"]).catch(err=>"")
|
||||||
|
]).then(function(output) {
|
||||||
|
var m = / (\d\S+)/.exec(output[0]);
|
||||||
|
gitVersion = m[1];
|
||||||
|
var globalUserName = output[1].trim();
|
||||||
|
var globalUserEmail = output[2].trim();
|
||||||
|
var result = {
|
||||||
|
version: gitVersion
|
||||||
|
};
|
||||||
|
if (globalUserName && globalUserEmail) {
|
||||||
|
result.user = {
|
||||||
|
name: globalUserName,
|
||||||
|
email: globalUserEmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(err);
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initRepo: function(cwd) {
|
||||||
|
return runGitCommand(["init"],cwd);
|
||||||
|
},
|
||||||
|
setUpstream: function(cwd,remoteBranch) {
|
||||||
|
var args = ["branch","--set-upstream-to",remoteBranch];
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
pull: function(cwd,remote,branch,auth) {
|
||||||
|
var args = ["pull"];
|
||||||
|
if (remote && branch) {
|
||||||
|
args.push(remote);
|
||||||
|
args.push(branch);
|
||||||
|
}
|
||||||
|
var promise;
|
||||||
|
if (auth) {
|
||||||
|
if ( auth.key_path ) {
|
||||||
|
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
promise = runGitCommand(args,cwd)
|
||||||
|
}
|
||||||
|
return promise.catch(function(err) {
|
||||||
|
if (/CONFLICT/.test(err.stdout)) {
|
||||||
|
var e = new Error("NLS: pull failed - merge conflict");
|
||||||
|
e.code = "git_pull_merge_conflict";
|
||||||
|
throw e;
|
||||||
|
} else if (/Please commit your changes or stash/.test(err.message)) {
|
||||||
|
var e = new Error("NLS: Pull failed - local changes would be overwritten");
|
||||||
|
e.code = "git_pull_overwrite";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
push: function(cwd,remote,branch,setUpstream, auth) {
|
||||||
|
var args = ["push"];
|
||||||
|
if (branch) {
|
||||||
|
if (setUpstream) {
|
||||||
|
args.push("-u");
|
||||||
|
}
|
||||||
|
args.push(remote);
|
||||||
|
args.push("HEAD:"+branch);
|
||||||
|
} else {
|
||||||
|
args.push(remote);
|
||||||
|
}
|
||||||
|
args.push("--porcelain");
|
||||||
|
var promise;
|
||||||
|
if (auth) {
|
||||||
|
if ( auth.key_path ) {
|
||||||
|
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
promise = runGitCommand(args,cwd)
|
||||||
|
}
|
||||||
|
return promise.catch(function(err) {
|
||||||
|
if (err.code === 'git_error') {
|
||||||
|
if (/^!.*non-fast-forward/m.test(err.stdout)) {
|
||||||
|
err.code = 'git_push_failed';
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clone: function(remote, auth, cwd) {
|
||||||
|
var args = ["clone",remote.url];
|
||||||
|
if (remote.name) {
|
||||||
|
args.push("-o");
|
||||||
|
args.push(remote.name);
|
||||||
|
}
|
||||||
|
if (remote.branch) {
|
||||||
|
args.push("-b");
|
||||||
|
args.push(remote.branch);
|
||||||
|
}
|
||||||
|
args.push(".");
|
||||||
|
if (auth) {
|
||||||
|
if ( auth.key_path ) {
|
||||||
|
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return runGitCommandWithAuth(args,cwd,auth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getStatus: getStatus,
|
||||||
|
getFile: function(cwd, filePath, treeish) {
|
||||||
|
var args = ["show",treeish+":"+filePath];
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
getFiles: function(cwd) {
|
||||||
|
return getStatus(cwd).then(function(status) {
|
||||||
|
return status.files;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
revertFile: function(cwd, filePath) {
|
||||||
|
var args = ["checkout",filePath];
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
stageFile: function(cwd,file) {
|
||||||
|
var args = ["add"];
|
||||||
|
if (Array.isArray(file)) {
|
||||||
|
args = args.concat(file);
|
||||||
|
} else {
|
||||||
|
args.push(file);
|
||||||
|
}
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
unstageFile: function(cwd, file) {
|
||||||
|
var args = ["reset","--"];
|
||||||
|
if (file) {
|
||||||
|
args.push(file);
|
||||||
|
}
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
commit: function(cwd, message, gitUser) {
|
||||||
|
var args = ["commit","-m",message];
|
||||||
|
var env;
|
||||||
|
if (gitUser && gitUser['name'] && gitUser['email']) {
|
||||||
|
args.unshift('user.name="'+gitUser['name']+'"');
|
||||||
|
args.unshift('-c');
|
||||||
|
args.unshift('user.email="'+gitUser['email']+'"');
|
||||||
|
args.unshift('-c');
|
||||||
|
}
|
||||||
|
return runGitCommand(args,cwd,env);
|
||||||
|
},
|
||||||
|
getFileDiff(cwd,file,type) {
|
||||||
|
var args = ["diff"];
|
||||||
|
if (type === "tree") {
|
||||||
|
// nothing else to do
|
||||||
|
} else if (type === "index") {
|
||||||
|
args.push("--cached");
|
||||||
|
}
|
||||||
|
args.push(file);
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
fetch: function(cwd,remote,auth) {
|
||||||
|
var args = ["fetch",remote];
|
||||||
|
if (auth) {
|
||||||
|
if ( auth.key_path ) {
|
||||||
|
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return runGitCommandWithAuth(args,cwd,auth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCommits: function(cwd,options) {
|
||||||
|
var args = ["log", "--format=sha: %H%nparents: %p%nrefs: %D%nauthor: %an%ndate: %ct%nsubject: %s%n-----"];
|
||||||
|
var limit = parseInt(options.limit) || 20;
|
||||||
|
args.push("-n "+limit);
|
||||||
|
var before = options.before;
|
||||||
|
if (before) {
|
||||||
|
args.push(before);
|
||||||
|
}
|
||||||
|
var commands = [
|
||||||
|
runGitCommand(['rev-list', 'HEAD', '--count'],cwd),
|
||||||
|
runGitCommand(args,cwd).then(parseLog)
|
||||||
|
];
|
||||||
|
return when.all(commands).then(function(results) {
|
||||||
|
var result = results[0];
|
||||||
|
result.count = results[1].length;
|
||||||
|
result.before = before;
|
||||||
|
result.commits = results[1];
|
||||||
|
return {
|
||||||
|
count: results[1].length,
|
||||||
|
commits: results[1],
|
||||||
|
before: before,
|
||||||
|
total: parseInt(results[0])
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getCommit: function(cwd,sha) {
|
||||||
|
var args = ["show",sha];
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
abortMerge: function(cwd) {
|
||||||
|
return runGitCommand(['merge','--abort'],cwd);
|
||||||
|
},
|
||||||
|
getRemotes: getRemotes,
|
||||||
|
getRemoteBranch: function(cwd) {
|
||||||
|
return runGitCommand(['rev-parse','--abbrev-ref','--symbolic-full-name','@{u}'],cwd).catch(function(err) {
|
||||||
|
if (/no upstream configured for branch/.test(err.message)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getBranches: getBranches,
|
||||||
|
// getBranchInfo: getBranchInfo,
|
||||||
|
checkoutBranch: function(cwd, branchName, isCreate) {
|
||||||
|
var args = ['checkout'];
|
||||||
|
if (isCreate) {
|
||||||
|
args.push('-b');
|
||||||
|
}
|
||||||
|
args.push(branchName);
|
||||||
|
return runGitCommand(args,cwd);
|
||||||
|
},
|
||||||
|
deleteBranch: function(cwd, branchName, isRemote, force) {
|
||||||
|
if (isRemote) {
|
||||||
|
throw new Error("Deleting remote branches not supported");
|
||||||
|
}
|
||||||
|
var args = ['branch'];
|
||||||
|
if (force) {
|
||||||
|
args.push('-D');
|
||||||
|
} else {
|
||||||
|
args.push('-d');
|
||||||
|
}
|
||||||
|
args.push(branchName);
|
||||||
|
return runGitCommand(args, cwd);
|
||||||
|
},
|
||||||
|
getBranchStatus: getBranchStatus,
|
||||||
|
addRemote: addRemote,
|
||||||
|
removeRemote: removeRemote
|
||||||
|
}
|
1
red/runtime/storage/localfilesystem/projects/git/node-red-ask-pass.sh
Executable file
1
red/runtime/storage/localfilesystem/projects/git/node-red-ask-pass.sh
Executable file
@ -0,0 +1 @@
|
|||||||
|
"$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $@
|
569
red/runtime/storage/localfilesystem/projects/index.js
Normal file
569
red/runtime/storage/localfilesystem/projects/index.js
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var when = require('when');
|
||||||
|
var fspath = require("path");
|
||||||
|
var nodeFn = require('when/node/function');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
|
var storageSettings = require("../settings");
|
||||||
|
var util = require("../util");
|
||||||
|
var gitTools = require("./git");
|
||||||
|
var sshTools = require("./ssh");
|
||||||
|
|
||||||
|
var Projects = require("./Project");
|
||||||
|
|
||||||
|
var settings;
|
||||||
|
var runtime;
|
||||||
|
var log;
|
||||||
|
|
||||||
|
var projectsEnabled = false;
|
||||||
|
var projectLogMessages = [];
|
||||||
|
|
||||||
|
var projectsDir;
|
||||||
|
var activeProject
|
||||||
|
|
||||||
|
var globalGitUser = false;
|
||||||
|
|
||||||
|
function init(_settings, _runtime) {
|
||||||
|
settings = _settings;
|
||||||
|
runtime = _runtime;
|
||||||
|
log = runtime.log;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (settings.editorTheme.projects.enabled === true) {
|
||||||
|
projectLogMessages.push(log._("storage.localfilesystem.projects.disabled"))
|
||||||
|
projectsEnabled = true;
|
||||||
|
} else if (settings.editorTheme.projects.enabled === false) {
|
||||||
|
projectLogMessages.push(log._("storage.localfilesystem.projects.disabled"))
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
projectLogMessages.push(log._("storage.localfilesystem.projects.disabledNoFlag"))
|
||||||
|
projectsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.flowFile) {
|
||||||
|
flowsFile = settings.flowFile;
|
||||||
|
// handle Unix and Windows "C:\"
|
||||||
|
if ((flowsFile[0] == "/") || (flowsFile[1] == ":")) {
|
||||||
|
// Absolute path
|
||||||
|
flowsFullPath = flowsFile;
|
||||||
|
} else if (flowsFile.substring(0,2) === "./") {
|
||||||
|
// Relative to cwd
|
||||||
|
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
fs.statSync(fspath.join(process.cwd(),flowsFile));
|
||||||
|
// Found in cwd
|
||||||
|
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
||||||
|
} catch(err) {
|
||||||
|
// Use userDir
|
||||||
|
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
flowsFile = 'flows_'+require('os').hostname()+'.json';
|
||||||
|
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||||
|
}
|
||||||
|
var ffExt = fspath.extname(flowsFullPath);
|
||||||
|
var ffBase = fspath.basename(flowsFullPath,ffExt);
|
||||||
|
|
||||||
|
flowsFileBackup = getBackupFilename(flowsFullPath);
|
||||||
|
credentialsFile = fspath.join(settings.userDir,ffBase+"_cred"+ffExt);
|
||||||
|
credentialsFileBackup = getBackupFilename(credentialsFile)
|
||||||
|
|
||||||
|
var setupProjectsPromise;
|
||||||
|
|
||||||
|
if (projectsEnabled) {
|
||||||
|
return sshTools.init(settings,runtime).then(function() {
|
||||||
|
gitTools.init(_settings, _runtime).then(function(gitConfig) {
|
||||||
|
if (!gitConfig) {
|
||||||
|
projectLogMessages.push(log._("storage.localfilesystem.projects.git-not-found"))
|
||||||
|
projectsEnabled = false;
|
||||||
|
} else {
|
||||||
|
globalGitUser = gitConfig.user;
|
||||||
|
Projects.init(settings,runtime);
|
||||||
|
sshTools.init(settings,runtime);
|
||||||
|
projectsDir = fspath.join(settings.userDir,"projects");
|
||||||
|
if (!settings.readOnly) {
|
||||||
|
return fs.ensureDir(projectsDir)
|
||||||
|
//TODO: this is accessing settings from storage directly as settings
|
||||||
|
// has not yet been initialised. That isn't ideal - can this be deferred?
|
||||||
|
.then(storageSettings.getSettings)
|
||||||
|
.then(function(globalSettings) {
|
||||||
|
var saveSettings = false;
|
||||||
|
if (!globalSettings.projects) {
|
||||||
|
globalSettings.projects = {
|
||||||
|
projects: {}
|
||||||
|
}
|
||||||
|
saveSettings = true;
|
||||||
|
} else {
|
||||||
|
activeProject = globalSettings.projects.activeProject;
|
||||||
|
}
|
||||||
|
if (settings.flowFile) {
|
||||||
|
if (globalSettings.projects.projects.hasOwnProperty(settings.flowFile)) {
|
||||||
|
activeProject = settings.flowFile;
|
||||||
|
globalSettings.projects.activeProject = settings.flowFile;
|
||||||
|
saveSettings = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!activeProject) {
|
||||||
|
projectLogMessages.push(log._("storage.localfilesystem.no-active-project"))
|
||||||
|
}
|
||||||
|
if (saveSettings) {
|
||||||
|
return storageSettings.saveSettings(globalSettings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserGitSettings(user) {
|
||||||
|
var userSettings = settings.getUserSettings(user)||{};
|
||||||
|
return userSettings.git;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBackupFilename(filename) {
|
||||||
|
var ffName = fspath.basename(filename);
|
||||||
|
var ffDir = fspath.dirname(filename);
|
||||||
|
return fspath.join(ffDir,"."+ffName+".backup");
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProject(name) {
|
||||||
|
return Projects.get(name).then(function(project) {
|
||||||
|
activeProject = project;
|
||||||
|
flowsFullPath = project.getFlowFile();
|
||||||
|
flowsFileBackup = project.getFlowFileBackup();
|
||||||
|
credentialsFile = project.getCredentialsFile();
|
||||||
|
credentialsFileBackup = project.getCredentialsFileBackup();
|
||||||
|
return project;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function listProjects(user) {
|
||||||
|
return Projects.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProject(user, name) {
|
||||||
|
checkActiveProject(name);
|
||||||
|
//return when.resolve(activeProject.info);
|
||||||
|
var username;
|
||||||
|
if (!user) {
|
||||||
|
username = "_";
|
||||||
|
} else {
|
||||||
|
username = user.username;
|
||||||
|
}
|
||||||
|
return Projects.get(name).then(function(project) {
|
||||||
|
return project.toJSON();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteProject(user, name) {
|
||||||
|
return Projects.delete(user, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkActiveProject(project) {
|
||||||
|
if (!activeProject || activeProject.name !== project) {
|
||||||
|
//TODO: throw better err
|
||||||
|
throw new Error("Cannot operate on inactive project wanted:"+project+" current:"+(activeProject&&activeProject.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getFiles(user, project) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getFiles();
|
||||||
|
}
|
||||||
|
function stageFile(user, project,file) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.stageFile(file);
|
||||||
|
}
|
||||||
|
function unstageFile(user, project,file) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.unstageFile(file);
|
||||||
|
}
|
||||||
|
function commit(user, project,options) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.commit(user, options);
|
||||||
|
}
|
||||||
|
function getFileDiff(user, project,file,type) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getFileDiff(file,type);
|
||||||
|
}
|
||||||
|
function getCommits(user, project,options) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getCommits(options);
|
||||||
|
}
|
||||||
|
function getCommit(user, project,sha) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getCommit(sha);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFile(user, project,filePath,sha) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getFile(filePath,sha);
|
||||||
|
}
|
||||||
|
function revertFile(user, project,filePath) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.revertFile(filePath).then(function() {
|
||||||
|
return reloadActiveProject("revert");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function push(user, project,remoteBranchName,setRemote) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.push(user,remoteBranchName,setRemote);
|
||||||
|
}
|
||||||
|
function pull(user, project,remoteBranchName,setRemote) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.pull(user,remoteBranchName,setRemote).then(function() {
|
||||||
|
return reloadActiveProject("pull");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getStatus(user, project) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.status(user);
|
||||||
|
}
|
||||||
|
function resolveMerge(user, project,file,resolution) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.resolveMerge(file,resolution);
|
||||||
|
}
|
||||||
|
function abortMerge(user, project) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.abortMerge().then(function() {
|
||||||
|
return reloadActiveProject("abort-merge")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getBranches(user, project,isRemote) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getBranches(user, isRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteBranch(user, project, branch, isRemote, force) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.deleteBranch(user, branch, isRemote, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBranch(user, project,branchName,isCreate) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.setBranch(branchName,isCreate).then(function() {
|
||||||
|
return reloadActiveProject("change-branch");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getBranchStatus(user, project,branchName) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getBranchStatus(branchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getRemotes(user, project) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.getRemotes(user);
|
||||||
|
}
|
||||||
|
function addRemote(user, project, options) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.addRemote(user, options.name, options);
|
||||||
|
}
|
||||||
|
function removeRemote(user, project, remote) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.removeRemote(user, remote);
|
||||||
|
}
|
||||||
|
function updateRemote(user, project, remote, body) {
|
||||||
|
checkActiveProject(project);
|
||||||
|
return activeProject.updateRemote(user, remote, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveProject(user) {
|
||||||
|
return activeProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadActiveProject(action) {
|
||||||
|
return runtime.nodes.stopFlows().then(function() {
|
||||||
|
return runtime.nodes.loadFlows(true).then(function() {
|
||||||
|
runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||||
|
}).catch(function(err) {
|
||||||
|
// We're committed to the project change now, so notify editors
|
||||||
|
// that it has changed.
|
||||||
|
runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function createProject(user, metadata) {
|
||||||
|
// var userSettings = getUserGitSettings(user);
|
||||||
|
if (metadata.files && metadata.files.migrateFiles) {
|
||||||
|
// We expect there to be no active project in this scenario
|
||||||
|
if (activeProject) {
|
||||||
|
throw new Error("Cannot migrate as there is an active project");
|
||||||
|
}
|
||||||
|
var currentEncryptionKey = settings.get('credentialSecret');
|
||||||
|
if (currentEncryptionKey === undefined) {
|
||||||
|
currentEncryptionKey = settings.get('_credentialSecret');
|
||||||
|
}
|
||||||
|
if (!metadata.hasOwnProperty('credentialSecret')) {
|
||||||
|
metadata.credentialSecret = currentEncryptionKey;
|
||||||
|
}
|
||||||
|
if (!metadata.files.flow) {
|
||||||
|
metadata.files.flow = fspath.basename(flowsFullPath);
|
||||||
|
}
|
||||||
|
if (!metadata.files.credentials) {
|
||||||
|
metadata.files.credentials = fspath.basename(credentialsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.files.oldFlow = flowsFullPath;
|
||||||
|
metadata.files.oldCredentials = credentialsFile;
|
||||||
|
metadata.files.credentialSecret = currentEncryptionKey;
|
||||||
|
}
|
||||||
|
return Projects.create(null,metadata).then(function(p) {
|
||||||
|
return setActiveProject(user, p.name);
|
||||||
|
}).then(function() {
|
||||||
|
return getProject(user, metadata.name);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function setActiveProject(user, projectName) {
|
||||||
|
return loadProject(projectName).then(function(project) {
|
||||||
|
var globalProjectSettings = settings.get("projects");
|
||||||
|
globalProjectSettings.activeProject = project.name;
|
||||||
|
return settings.set("projects",globalProjectSettings).then(function() {
|
||||||
|
log.info(log._("storage.localfilesystem.projects.changing-project",{project:(activeProject&&activeProject.name)||"none"}));
|
||||||
|
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||||
|
// console.log("Updated file targets to");
|
||||||
|
// console.log(flowsFullPath)
|
||||||
|
// console.log(credentialsFile)
|
||||||
|
return reloadActiveProject("loaded");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialiseProject(user, project, data) {
|
||||||
|
if (!activeProject || activeProject.name !== project) {
|
||||||
|
// TODO standardise
|
||||||
|
throw new Error("Cannot initialise inactive project");
|
||||||
|
}
|
||||||
|
return activeProject.initialise(user,data).then(function(result) {
|
||||||
|
flowsFullPath = activeProject.getFlowFile();
|
||||||
|
flowsFileBackup = activeProject.getFlowFileBackup();
|
||||||
|
credentialsFile = activeProject.getCredentialsFile();
|
||||||
|
credentialsFileBackup = activeProject.getCredentialsFileBackup();
|
||||||
|
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||||
|
return reloadActiveProject("updated");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function updateProject(user, project, data) {
|
||||||
|
if (!activeProject || activeProject.name !== project) {
|
||||||
|
// TODO standardise
|
||||||
|
throw new Error("Cannot update inactive project");
|
||||||
|
}
|
||||||
|
// In case this triggers a credential secret change
|
||||||
|
var isReset = data.resetCredentialSecret;
|
||||||
|
var wasInvalid = activeProject.credentialSecretInvalid;
|
||||||
|
|
||||||
|
return activeProject.update(user,data).then(function(result) {
|
||||||
|
|
||||||
|
if (result.flowFilesChanged) {
|
||||||
|
flowsFullPath = activeProject.getFlowFile();
|
||||||
|
flowsFileBackup = activeProject.getFlowFileBackup();
|
||||||
|
credentialsFile = activeProject.getCredentialsFile();
|
||||||
|
credentialsFileBackup = activeProject.getCredentialsFileBackup();
|
||||||
|
return reloadActiveProject("updated");
|
||||||
|
} else if (result.credentialSecretChanged) {
|
||||||
|
if (isReset || !wasInvalid) {
|
||||||
|
if (isReset) {
|
||||||
|
runtime.nodes.clearCredentials();
|
||||||
|
}
|
||||||
|
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||||
|
return runtime.nodes.exportCredentials()
|
||||||
|
.then(runtime.storage.saveCredentials)
|
||||||
|
.then(function() {
|
||||||
|
if (wasInvalid) {
|
||||||
|
return reloadActiveProject("updated");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (wasInvalid) {
|
||||||
|
return reloadActiveProject("updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function setCredentialSecret(data) { //existingSecret,secret) {
|
||||||
|
var isReset = data.resetCredentialSecret;
|
||||||
|
var wasInvalid = activeProject.credentialSecretInvalid;
|
||||||
|
return activeProject.update(data).then(function() {
|
||||||
|
if (isReset || !wasInvalid) {
|
||||||
|
if (isReset) {
|
||||||
|
runtime.nodes.clearCredentials();
|
||||||
|
}
|
||||||
|
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||||
|
return runtime.nodes.exportCredentials()
|
||||||
|
.then(runtime.storage.saveCredentials)
|
||||||
|
.then(function() {
|
||||||
|
if (wasInvalid) {
|
||||||
|
return reloadActiveProject("updated");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (wasInvalid) {
|
||||||
|
return reloadActiveProject("updated");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var initialFlowLoadComplete = false;
|
||||||
|
|
||||||
|
var flowsFile;
|
||||||
|
var flowsFullPath;
|
||||||
|
var flowsFileBackup;
|
||||||
|
var credentialsFile;
|
||||||
|
var credentialsFileBackup;
|
||||||
|
|
||||||
|
function getFlows() {
|
||||||
|
if (!initialFlowLoadComplete) {
|
||||||
|
initialFlowLoadComplete = true;
|
||||||
|
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
||||||
|
if (activeProject) {
|
||||||
|
// At this point activeProject will be a string, so go load it and
|
||||||
|
// swap in an instance of Project
|
||||||
|
return loadProject(activeProject).then(function() {
|
||||||
|
log.info(log._("storage.localfilesystem.projects.active-project",{project:activeProject.name||"none"}));
|
||||||
|
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||||
|
return getFlows();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (projectsEnabled) {
|
||||||
|
log.warn(log._("storage.localfilesystem.projects.no-active-project"))
|
||||||
|
} else {
|
||||||
|
projectLogMessages.forEach(log.warn);
|
||||||
|
}
|
||||||
|
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activeProject) {
|
||||||
|
var error;
|
||||||
|
if (activeProject.isEmpty()) {
|
||||||
|
log.warn("Project repository is empty");
|
||||||
|
error = new Error("Project repository is empty");
|
||||||
|
error.code = "project_empty";
|
||||||
|
return when.reject(error);
|
||||||
|
}
|
||||||
|
if (!activeProject.getFlowFile()) {
|
||||||
|
log.warn("Project has no flow file");
|
||||||
|
error = new Error("Project has no flow file");
|
||||||
|
error.code = "missing_flow_file";
|
||||||
|
return when.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.readFile(flowsFullPath,flowsFileBackup,[],'flow');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveFlows(flows) {
|
||||||
|
if (settings.readOnly) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.renameSync(flowsFullPath,flowsFileBackup);
|
||||||
|
} catch(err) {
|
||||||
|
}
|
||||||
|
|
||||||
|
var flowData;
|
||||||
|
|
||||||
|
if (settings.flowFilePretty) {
|
||||||
|
flowData = JSON.stringify(flows,null,4);
|
||||||
|
} else {
|
||||||
|
flowData = JSON.stringify(flows);
|
||||||
|
}
|
||||||
|
return util.writeFile(flowsFullPath, flowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCredentials() {
|
||||||
|
return util.readFile(credentialsFile,credentialsFileBackup,{},'credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCredentials(credentials) {
|
||||||
|
if (settings.readOnly) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.renameSync(credentialsFile,credentialsFileBackup);
|
||||||
|
} catch(err) {
|
||||||
|
}
|
||||||
|
var credentialData;
|
||||||
|
if (settings.flowFilePretty) {
|
||||||
|
credentialData = JSON.stringify(credentials,null,4);
|
||||||
|
} else {
|
||||||
|
credentialData = JSON.stringify(credentials);
|
||||||
|
}
|
||||||
|
return util.writeFile(credentialsFile, credentialData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowFilename() {
|
||||||
|
if (flowsFullPath) {
|
||||||
|
return fspath.basename(flowsFullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getCredentialsFilename() {
|
||||||
|
if (flowsFullPath) {
|
||||||
|
return fspath.basename(credentialsFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
listProjects: listProjects,
|
||||||
|
getActiveProject: getActiveProject,
|
||||||
|
setActiveProject: setActiveProject,
|
||||||
|
getProject: getProject,
|
||||||
|
deleteProject: deleteProject,
|
||||||
|
createProject: createProject,
|
||||||
|
initialiseProject: initialiseProject,
|
||||||
|
updateProject: updateProject,
|
||||||
|
getFiles: getFiles,
|
||||||
|
getFile: getFile,
|
||||||
|
revertFile: revertFile,
|
||||||
|
stageFile: stageFile,
|
||||||
|
unstageFile: unstageFile,
|
||||||
|
commit: commit,
|
||||||
|
getFileDiff: getFileDiff,
|
||||||
|
getCommits: getCommits,
|
||||||
|
getCommit: getCommit,
|
||||||
|
push: push,
|
||||||
|
pull: pull,
|
||||||
|
getStatus:getStatus,
|
||||||
|
resolveMerge: resolveMerge,
|
||||||
|
abortMerge: abortMerge,
|
||||||
|
getBranches: getBranches,
|
||||||
|
deleteBranch: deleteBranch,
|
||||||
|
setBranch: setBranch,
|
||||||
|
getBranchStatus:getBranchStatus,
|
||||||
|
getRemotes: getRemotes,
|
||||||
|
addRemote: addRemote,
|
||||||
|
removeRemote: removeRemote,
|
||||||
|
updateRemote: updateRemote,
|
||||||
|
getFlowFilename: getFlowFilename,
|
||||||
|
getCredentialsFilename: getCredentialsFilename,
|
||||||
|
getGlobalGitUser: function() { return globalGitUser },
|
||||||
|
getFlows: getFlows,
|
||||||
|
saveFlows: saveFlows,
|
||||||
|
getCredentials: getCredentials,
|
||||||
|
saveCredentials: saveCredentials,
|
||||||
|
|
||||||
|
ssh: sshTools
|
||||||
|
|
||||||
|
};
|
209
red/runtime/storage/localfilesystem/projects/ssh/index.js
Normal file
209
red/runtime/storage/localfilesystem/projects/ssh/index.js
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var when = require('when');
|
||||||
|
var fspath = require("path");
|
||||||
|
var keygen = require("./keygen");
|
||||||
|
|
||||||
|
var settings;
|
||||||
|
var runtime;
|
||||||
|
var log;
|
||||||
|
var sshkeyDir;
|
||||||
|
var userSSHKeyDir;
|
||||||
|
|
||||||
|
function init(_settings, _runtime) {
|
||||||
|
settings = _settings;
|
||||||
|
runtime = _runtime;
|
||||||
|
log = runtime.log;
|
||||||
|
sshkeyDir = fspath.join(settings.userDir, "projects", ".sshkeys");
|
||||||
|
userSSHKeyDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, ".ssh");
|
||||||
|
// console.log('sshkeys.init()');
|
||||||
|
return fs.ensureDir(sshkeyDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSSHKeys(username) {
|
||||||
|
return listSSHKeysInDir(sshkeyDir,username + '_').then(function(customKeys) {
|
||||||
|
return listSSHKeysInDir(userSSHKeyDir).then(function(existingKeys) {
|
||||||
|
existingKeys.forEach(function(k){
|
||||||
|
k.system = true;
|
||||||
|
customKeys.push(k);
|
||||||
|
})
|
||||||
|
return customKeys;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSSHKeysInDir(dir,startStr) {
|
||||||
|
startStr = startStr || "";
|
||||||
|
return fs.readdir(dir).then(function(fns) {
|
||||||
|
var ret = fns.sort()
|
||||||
|
.filter(function(fn) {
|
||||||
|
var fullPath = fspath.join(dir,fn);
|
||||||
|
if (fn.length > 2 || fn[0] != ".") {
|
||||||
|
var stats = fs.lstatSync(fullPath);
|
||||||
|
if (stats.isFile()) {
|
||||||
|
return fn.startsWith(startStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.map(function(filename) {
|
||||||
|
return filename.substr(startStr.length);
|
||||||
|
})
|
||||||
|
.reduce(function(prev, current) {
|
||||||
|
var parsePath = fspath.parse(current);
|
||||||
|
if ( parsePath ) {
|
||||||
|
if ( parsePath.ext !== '.pub' ) {
|
||||||
|
// Private Keys
|
||||||
|
prev.keyFiles.push(parsePath.base);
|
||||||
|
}
|
||||||
|
else if ( parsePath.ext === '.pub' && (prev.keyFiles.some(function(elem){ return elem === parsePath.name; }))) {
|
||||||
|
prev.privateKeyFiles.push(parsePath.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, { keyFiles: [], privateKeyFiles: [] });
|
||||||
|
return ret.privateKeyFiles.map(function(filename) {
|
||||||
|
return {
|
||||||
|
name: filename
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}).then(function(result) {
|
||||||
|
return result;
|
||||||
|
}).catch(function() {
|
||||||
|
return []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSSHKey(username, name) {
|
||||||
|
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
|
||||||
|
.then(function(publicSSHKeyPath) {
|
||||||
|
return fs.readFile(publicSSHKeyPath, 'utf-8');
|
||||||
|
}).catch(function() {
|
||||||
|
var privateKeyPath = fspath.join(userSSHKeyDir,name);
|
||||||
|
var publicKeyPath = privateKeyPath+".pub";
|
||||||
|
return checkFilePairExist(privateKeyPath,publicKeyPath).then(function() {
|
||||||
|
return fs.readFile(publicKeyPath, 'utf-8');
|
||||||
|
}).catch(function() {
|
||||||
|
return null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSSHKey(username, options) {
|
||||||
|
options = options || {};
|
||||||
|
var name = options.name || "";
|
||||||
|
return checkExistSSHKeyFiles(username, name)
|
||||||
|
.then(function(result) {
|
||||||
|
if ( result ) {
|
||||||
|
var e = new Error("SSH Key name exists");
|
||||||
|
e.code = "key_exists";
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
var comment = options.comment || "";
|
||||||
|
var password = options.password || "";
|
||||||
|
var size = options.size || 2048;
|
||||||
|
var sshKeyFileBasename = username + '_' + name;
|
||||||
|
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||||
|
return generateSSHKeyPair(name, privateKeyFilePath, comment, password, size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSSHKey(username, name) {
|
||||||
|
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
|
||||||
|
.then(function() {
|
||||||
|
return deleteSSHKeyFiles(username, name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkExistSSHKeyFiles(username, name) {
|
||||||
|
var sshKeyFileBasename = username + '_' + name;
|
||||||
|
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||||
|
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||||
|
return checkFilePairExist(privateKeyFilePath,publicKeyFilePath)
|
||||||
|
.then(function() {
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSSHKeyFileAndGetPublicKeyFileName(username, name) {
|
||||||
|
var sshKeyFileBasename = username + '_' + name;
|
||||||
|
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||||
|
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||||
|
return checkFilePairExist(privateKeyFilePath,publicKeyFilePath).then(function() {
|
||||||
|
return publicKeyFilePath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkFilePairExist(privateKeyFilePath,publicKeyFilePath) {
|
||||||
|
return Promise.all([
|
||||||
|
fs.access(privateKeyFilePath, (fs.constants || fs).R_OK),
|
||||||
|
fs.access(publicKeyFilePath , (fs.constants || fs).R_OK)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSSHKeyFiles(username, name) {
|
||||||
|
var sshKeyFileBasename = username + '_' + name;
|
||||||
|
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||||
|
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||||
|
return Promise.all([
|
||||||
|
fs.remove(privateKeyFilePath),
|
||||||
|
fs.remove(publicKeyFilePath)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSSHKeyPair(name, privateKeyPath, comment, password, size) {
|
||||||
|
log.trace("ssh-keygen["+[name,privateKeyPath,comment,size,"hasPassword?"+!!password].join(",")+"]");
|
||||||
|
return keygen.generateKey({location: privateKeyPath, comment: comment, password: password, size: size})
|
||||||
|
.then(function(stdout) {
|
||||||
|
return name;
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.log('[SSHKey generation] error:', err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrivateKeyPath(username, name) {
|
||||||
|
var sshKeyFileBasename = username + '_' + name;
|
||||||
|
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||||
|
try {
|
||||||
|
fs.accessSync(privateKeyFilePath, (fs.constants || fs).R_OK);
|
||||||
|
return privateKeyFilePath;
|
||||||
|
} catch(err) {
|
||||||
|
privateKeyFilePath = fspath.join(userSSHKeyDir,name);
|
||||||
|
try {
|
||||||
|
fs.accessSync(privateKeyFilePath, (fs.constants || fs).R_OK);
|
||||||
|
return privateKeyFilePath;
|
||||||
|
} catch(err2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
listSSHKeys: listSSHKeys,
|
||||||
|
getSSHKey: getSSHKey,
|
||||||
|
getPrivateKeyPath: getPrivateKeyPath,
|
||||||
|
generateSSHKey: generateSSHKey,
|
||||||
|
deleteSSHKey: deleteSSHKey
|
||||||
|
};
|
95
red/runtime/storage/localfilesystem/projects/ssh/keygen.js
Normal file
95
red/runtime/storage/localfilesystem/projects/ssh/keygen.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var child_process = require('child_process');
|
||||||
|
|
||||||
|
var sshkeygenCommand = "ssh-keygen";
|
||||||
|
|
||||||
|
var log;
|
||||||
|
|
||||||
|
function runSshKeygenCommand(args,cwd,env) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var child = child_process.spawn(sshkeygenCommand, args, {cwd: cwd, detached: true, env: env});
|
||||||
|
var stdout = "";
|
||||||
|
var stderr = "";
|
||||||
|
|
||||||
|
child.stdout.on('data', function(data) {
|
||||||
|
stdout += data;
|
||||||
|
});
|
||||||
|
child.stderr.on('data', function(data) {
|
||||||
|
stderr += data;
|
||||||
|
});
|
||||||
|
child.on('close', function(code, signal) {
|
||||||
|
// console.log(code);
|
||||||
|
// console.log(stdout);
|
||||||
|
// console.log(stderr);
|
||||||
|
if (code !== 0) {
|
||||||
|
var err = new Error(stderr);
|
||||||
|
err.stdout = stdout;
|
||||||
|
err.stderr = stderr;
|
||||||
|
if (/short/.test(stderr)) {
|
||||||
|
err.code = "key_passphrase_too_short";
|
||||||
|
} else if(/Key must at least be 1024 bits/.test(stderr)) {
|
||||||
|
err.code = "key_length_too_short";
|
||||||
|
}
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve(stdout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(_settings, _runtime) {
|
||||||
|
log = _runtime.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateKey(options) {
|
||||||
|
var args = ['-q', '-t', 'rsa'];
|
||||||
|
var err;
|
||||||
|
if (options.size) {
|
||||||
|
if (options.size < 1024) {
|
||||||
|
err = new Error("key_length_too_short");
|
||||||
|
err.code = "key_length_too_short";
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
args.push('-b', options.size);
|
||||||
|
}
|
||||||
|
if (options.location) {
|
||||||
|
args.push('-f', options.location);
|
||||||
|
}
|
||||||
|
if (options.comment) {
|
||||||
|
args.push('-C', options.comment);
|
||||||
|
}
|
||||||
|
if (options.password) {
|
||||||
|
if (options.password.length < 5) {
|
||||||
|
err = new Error("key_passphrase_too_short");
|
||||||
|
err.code = "key_passphrase_too_short";
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
args.push('-N', options.password||'');
|
||||||
|
} else {
|
||||||
|
args.push('-N', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return runSshKeygenCommand(args,__dirname);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
generateKey: generateKey,
|
||||||
|
};
|
52
red/runtime/storage/localfilesystem/sessions.js
Normal file
52
red/runtime/storage/localfilesystem/sessions.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var when = require('when');
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var fspath = require("path");
|
||||||
|
|
||||||
|
var log = require("../../log");
|
||||||
|
var util = require("./util");
|
||||||
|
|
||||||
|
var sessionsFile;
|
||||||
|
var settings;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_settings) {
|
||||||
|
settings = _settings;
|
||||||
|
sessionsFile = fspath.join(settings.userDir,".sessions.json");
|
||||||
|
},
|
||||||
|
getSessions: function() {
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
fs.readFile(sessionsFile,'utf8',function(err,data){
|
||||||
|
if (!err) {
|
||||||
|
try {
|
||||||
|
return resolve(util.parseJSON(data));
|
||||||
|
} catch(err2) {
|
||||||
|
log.trace("Corrupted sessions file - resetting");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve({});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
saveSessions: function(sessions) {
|
||||||
|
if (settings.readOnly) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
return util.writeFile(sessionsFile,JSON.stringify(sessions));
|
||||||
|
}
|
||||||
|
}
|
52
red/runtime/storage/localfilesystem/settings.js
Normal file
52
red/runtime/storage/localfilesystem/settings.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var when = require('when');
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var fspath = require("path");
|
||||||
|
|
||||||
|
var log = require("../../log");
|
||||||
|
var util = require("./util");
|
||||||
|
|
||||||
|
var globalSettingsFile;
|
||||||
|
var settings;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(_settings) {
|
||||||
|
settings = _settings;
|
||||||
|
globalSettingsFile = fspath.join(settings.userDir,".config.json");
|
||||||
|
},
|
||||||
|
getSettings: function() {
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
fs.readFile(globalSettingsFile,'utf8',function(err,data) {
|
||||||
|
if (!err) {
|
||||||
|
try {
|
||||||
|
return resolve(util.parseJSON(data));
|
||||||
|
} catch(err2) {
|
||||||
|
log.trace("Corrupted config detected - resetting");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolve({});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
saveSettings: function(newSettings) {
|
||||||
|
if (settings.readOnly) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1));
|
||||||
|
}
|
||||||
|
}
|
102
red/runtime/storage/localfilesystem/util.js
Normal file
102
red/runtime/storage/localfilesystem/util.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
var when = require('when');
|
||||||
|
var nodeFn = require('when/node/function');
|
||||||
|
|
||||||
|
var log = require("../../log");
|
||||||
|
|
||||||
|
function parseJSON(data) {
|
||||||
|
if (data.charCodeAt(0) === 0xFEFF) {
|
||||||
|
data = data.slice(1)
|
||||||
|
}
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
function readFile(path,backupPath,emptyResponse,type) {
|
||||||
|
return when.promise(function(resolve) {
|
||||||
|
fs.readFile(path,'utf8',function(err,data) {
|
||||||
|
if (!err) {
|
||||||
|
if (data.length === 0) {
|
||||||
|
log.warn(log._("storage.localfilesystem.empty",{type:type}));
|
||||||
|
try {
|
||||||
|
var backupStat = fs.statSync(backupPath);
|
||||||
|
if (backupStat.size === 0) {
|
||||||
|
// Empty flows, empty backup - return empty flow
|
||||||
|
return resolve(emptyResponse);
|
||||||
|
}
|
||||||
|
// Empty flows, restore backup
|
||||||
|
log.warn(log._("storage.localfilesystem.restore",{path:backupPath,type:type}));
|
||||||
|
fs.copy(backupPath,path,function(backupCopyErr) {
|
||||||
|
if (backupCopyErr) {
|
||||||
|
// Restore backup failed
|
||||||
|
log.warn(log._("storage.localfilesystem.restore-fail",{message:backupCopyErr.toString(),type:type}));
|
||||||
|
resolve([]);
|
||||||
|
} else {
|
||||||
|
// Loop back in to load the restored backup
|
||||||
|
resolve(readFile(path,backupPath,emptyResponse,type));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch(backupStatErr) {
|
||||||
|
// Empty flow file, no back-up file
|
||||||
|
return resolve(emptyResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return resolve(parseJSON(data));
|
||||||
|
} catch(parseErr) {
|
||||||
|
log.warn(log._("storage.localfilesystem.invalid",{type:type}));
|
||||||
|
return resolve(emptyResponse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (type === 'flow') {
|
||||||
|
log.info(log._("storage.localfilesystem.create",{type:type}));
|
||||||
|
}
|
||||||
|
resolve(emptyResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Write content to a file using UTF8 encoding.
|
||||||
|
* This forces a fsync before completing to ensure
|
||||||
|
* the write hits disk.
|
||||||
|
*/
|
||||||
|
writeFile: function(path,content) {
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
var stream = fs.createWriteStream(path);
|
||||||
|
stream.on('open',function(fd) {
|
||||||
|
stream.write(content,'utf8',function() {
|
||||||
|
fs.fsync(fd,function(err) {
|
||||||
|
if (err) {
|
||||||
|
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()}));
|
||||||
|
}
|
||||||
|
stream.end(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
stream.on('error',function(err) {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
readFile: readFile,
|
||||||
|
|
||||||
|
parseJSON: parseJSON
|
||||||
|
}
|
@ -24,70 +24,61 @@
|
|||||||
* TODO: Increase the scope of this check
|
* TODO: Increase the scope of this check
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var fs = require("fs");
|
var fs = require("fs-extra");
|
||||||
var should = require("should");
|
var should = require("should");
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
// Directories to check with .js files and _spec.js files respectively
|
// Directories to check with .js files and _spec.js files respectively
|
||||||
|
var rootdir = path.resolve(__dirname, "..");
|
||||||
var jsdir = path.resolve(__dirname, "../red");
|
var jsdir = path.resolve(__dirname, "../red");
|
||||||
var testdir = path.resolve(__dirname, "red");
|
var testdir = path.resolve(__dirname, "red");
|
||||||
|
|
||||||
var fs = require('fs');
|
var walkDirectory = function(dir) {
|
||||||
var walkDirectory = function(dir, topdir, done) {
|
var p = fs.readdir(dir);
|
||||||
fs.readdir(dir, function(err, list) {
|
var errors = [];
|
||||||
var error;
|
return p.then(function(list) {
|
||||||
var errReturned = false;
|
var promises = [];
|
||||||
if (err) {
|
list.forEach(function(file) {
|
||||||
return done(err);
|
var filePath = path.join(dir,file);
|
||||||
}
|
promises.push(fs.stat(filePath).then(function(stat){
|
||||||
|
if (stat.isDirectory()) {
|
||||||
var i = 0;
|
return walkDirectory(filePath).then(function(results) {
|
||||||
(function next() {
|
if (results) {
|
||||||
var file = list[i++];
|
errors = errors.concat(results);
|
||||||
|
|
||||||
// return error if there are no more files to check and error has not been previously returned to avoid multiple calls to done()
|
|
||||||
if (!file) {
|
|
||||||
if (!errReturned) {
|
|
||||||
errReturned = true;
|
|
||||||
return done(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file = path.resolve(dir, file);
|
|
||||||
fs.stat(file, function(err, stat) {
|
|
||||||
if (stat && stat.isDirectory()) {
|
|
||||||
walkDirectory(file, false, function(err) {
|
|
||||||
if (!error) {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (path.extname(file) === ".js") {
|
|
||||||
var testFile = file.replace(jsdir, testdir).replace(".js", "_spec.js");
|
|
||||||
fs.exists(testFile, function (exists) {
|
|
||||||
try {
|
|
||||||
exists.should.equal(true, testFile + " does not exist");
|
|
||||||
} catch (err) {
|
|
||||||
if (!topdir) {
|
|
||||||
return done(err);
|
|
||||||
} else {
|
|
||||||
error = err;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
next();
|
});
|
||||||
}
|
} else if (/\.js$/.test(filePath)) {
|
||||||
});
|
var testFile = filePath.replace(jsdir, testdir).replace(".js", "_spec.js");
|
||||||
}
|
return fs.exists(testFile).then(function(exists) {
|
||||||
})();
|
if (!exists) {
|
||||||
|
errors.push(testFile.substring(rootdir.length+1));
|
||||||
|
} else {
|
||||||
|
return fs.stat(testFile).then(function(stat) {
|
||||||
|
if (stat.size === 0) {
|
||||||
|
errors.push("[empty] "+testFile.substring(rootdir.length+1));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
return Promise.all(promises).then(function() {
|
||||||
|
return errors;
|
||||||
|
})
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
describe('_spec.js', function() {
|
describe('_spec.js', function() {
|
||||||
this.timeout(50000); // we might not finish within the Mocha default timeout limit, project will also grow
|
this.timeout(50000); // we might not finish within the Mocha default timeout limit, project will also grow
|
||||||
it('is checking if all .js files have a corresponding _spec.js test file.', function(done) {
|
it('is checking if all .js files have a corresponding _spec.js test file.', function(done) {
|
||||||
walkDirectory(jsdir, true, done);
|
walkDirectory(jsdir).then(function(errors) {
|
||||||
|
if (errors.length > 0) {
|
||||||
|
var error = new Error("Missing/empty _spec files:\n\t"+errors.join("\n\t"));
|
||||||
|
done(error);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -33,7 +33,7 @@ var RED = require("../../red/red.js");
|
|||||||
var redNodes = require("../../red/runtime/nodes");
|
var redNodes = require("../../red/runtime/nodes");
|
||||||
var flows = require("../../red/runtime/nodes/flows");
|
var flows = require("../../red/runtime/nodes/flows");
|
||||||
var credentials = require("../../red/runtime/nodes/credentials");
|
var credentials = require("../../red/runtime/nodes/credentials");
|
||||||
var comms = require("../../red/api/comms.js");
|
var comms = require("../../red/api/editor/comms.js");
|
||||||
var log = require("../../red/runtime/log.js");
|
var log = require("../../red/runtime/log.js");
|
||||||
var context = require("../../red/runtime/nodes/context.js");
|
var context = require("../../red/runtime/nodes/context.js");
|
||||||
var events = require("../../red/runtime/events.js");
|
var events = require("../../red/runtime/events.js");
|
||||||
|
@ -21,9 +21,9 @@ var bodyParser = require('body-parser');
|
|||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var when = require('when');
|
var when = require('when');
|
||||||
|
|
||||||
var flow = require("../../../red/api/flow");
|
var flow = require("../../../../red/api/admin/flow");
|
||||||
|
|
||||||
describe("flow api", function() {
|
describe("api/admin/flow", function() {
|
||||||
|
|
||||||
var app;
|
var app;
|
||||||
|
|
@ -21,9 +21,9 @@ var bodyParser = require('body-parser');
|
|||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var when = require('when');
|
var when = require('when');
|
||||||
|
|
||||||
var flows = require("../../../red/api/flows");
|
var flows = require("../../../../red/api/admin/flows");
|
||||||
|
|
||||||
describe("flows api", function() {
|
describe("api/admin/flows", function() {
|
||||||
|
|
||||||
var app;
|
var app;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user