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

Add commit-diff view

This commit is contained in:
Nick O'Leary 2017-10-25 15:26:24 +01:00
parent b9a3563e5b
commit 57533fd831
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
14 changed files with 617 additions and 273 deletions

View File

@ -21,15 +21,9 @@ RED.diff = (function() {
RED.keyboard.add("*","ctrl-shift-f 3","core:show-test-flow-diff-3");
}
function buildDiffPanel(container) {
var diffPanel = $('<div id="node-dialog-view-diff"><div id="node-dialog-view-diff-headers"></div><ol id="node-dialog-view-diff-diff"></ol></div>').appendTo(container);
var toolbar = $('<div class="node-diff-toolbar">'+
'<span><span id="node-diff-toolbar-resolved-conflicts"></span></span> '+
'</div>').prependTo(diffPanel);
diffList = diffPanel.find("#node-dialog-view-diff-diff").editableList({
function createDiffTable(container) {
var diffList = $('<ol class="node-dialog-view-diff-diff"></ol>').appendTo(container);
diffList.editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(container,i,object) {
@ -290,7 +284,180 @@ RED.diff = (function() {
container.i18n();
}
});
return diffPanel;
return diffList;
}
function buildDiffPanel(container,diff,options) {
var diffPanel = $('<div class="node-dialog-view-diff-panel"></div>').appendTo(container);
var diffHeaders = $('<div class="node-dialog-view-diff-headers"></div>').appendTo(diffPanel);
if (options.mode === "merge") {
diffPanel.addClass("node-dialog-view-diff-panel-merge");
var toolbar = $('<div class="node-diff-toolbar">'+
'<span><span id="node-diff-toolbar-resolved-conflicts"></span></span> '+
'</div>').prependTo(diffPanel);
}
var diffList = createDiffTable(diffPanel);
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
var conflicts = diff.conflicts;
var currentConfig = localDiff.currentConfig;
var newConfig = localDiff.newConfig;
if (remoteDiff !== undefined) {
diffPanel.addClass('node-diff-three-way');
var localTitle = options.oldRevTitle || RED._('diff.local');
var remoteTitle = options.newRevTitle || RED._('diff.remote');
$('<div></div>').text(localTitle).appendTo(diffHeaders);
$('<div></div>').text(remoteTitle).appendTo(diffHeaders);
} else {
diffPanel.removeClass('node-diff-three-way');
}
return {
list: diffList,
finish: function() {
var el = {
diff: localDiff,
def: {
category: 'config',
color: '#f0f0f0'
},
tab: {
n: {},
nodes: currentConfig.globals
},
newTab: {
n: {},
nodes: newConfig.globals
}
};
if (remoteDiff !== undefined) {
el.remoteTab = {
n:{},
nodes:remoteDiff.newConfig.globals
};
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el);
var seenTabs = {};
currentConfig.tabOrder.forEach(function(tabId) {
var tab = currentConfig.tabs[tabId];
var el = {
diff: localDiff,
def: RED.nodes.getType('tab'),
tab:tab
};
if (newConfig.tabs.hasOwnProperty(tabId)) {
el.newTab = newConfig.tabs[tabId];
}
if (remoteDiff !== undefined) {
el.remoteTab = remoteDiff.newConfig.tabs[tabId];
el.remoteDiff = remoteDiff;
}
seenTabs[tabId] = true;
diffList.editableList('addItem',el)
});
newConfig.tabOrder.forEach(function(tabId) {
if (!seenTabs[tabId]) {
seenTabs[tabId] = true;
var tab = newConfig.tabs[tabId];
var el = {
diff: localDiff,
def: RED.nodes.getType('tab'),
tab:tab,
newTab: tab
};
if (remoteDiff !== undefined) {
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el)
}
});
if (remoteDiff !== undefined) {
remoteDiff.newConfig.tabOrder.forEach(function(tabId) {
if (!seenTabs[tabId]) {
var tab = remoteDiff.newConfig.tabs[tabId];
// TODO how to recognise this is a remotely added flow
var el = {
diff: localDiff,
remoteDiff: remoteDiff,
def: RED.nodes.getType('tab'),
tab:tab,
remoteTab:tab
};
diffList.editableList('addItem',el)
}
});
}
var subflowId;
for (subflowId in currentConfig.subflows) {
if (currentConfig.subflows.hasOwnProperty(subflowId)) {
seenTabs[subflowId] = true;
el = {
diff: localDiff,
def: {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9"
},
tab:currentConfig.subflows[subflowId]
}
if (newConfig.subflows.hasOwnProperty(subflowId)) {
el.newTab = newConfig.subflows[subflowId];
}
if (remoteDiff !== undefined) {
el.remoteTab = remoteDiff.newConfig.subflows[subflowId];
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el)
}
}
for (subflowId in newConfig.subflows) {
if (newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) {
seenTabs[subflowId] = true;
el = {
diff: localDiff,
def: {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9"
},
tab:newConfig.subflows[subflowId],
newTab:newConfig.subflows[subflowId]
}
if (remoteDiff !== undefined) {
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el)
}
}
if (remoteDiff !== undefined) {
for (subflowId in remoteDiff.newConfig.subflows) {
if (remoteDiff.newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) {
el = {
diff: localDiff,
remoteDiff: remoteDiff,
def: {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9"
},
tab:remoteDiff.newConfig.subflows[subflowId],
remoteTab: remoteDiff.newConfig.subflows[subflowId]
}
diffList.editableList('addItem',el)
}
}
}
}
};
}
function formatWireProperty(wires,allNodes) {
var result = $("<div>",{class:"node-diff-property-wires"})
@ -917,7 +1084,7 @@ RED.diff = (function() {
if (diff === undefined) {
getRemoteDiff(showRemoteDiff);
} else {
showDiff(diff);
showDiff(diff,{mode:'merge'});
}
}
function parseNodes(nodeList) {
@ -1054,10 +1221,12 @@ RED.diff = (function() {
return diff;
}
function showDiff(diff) {
function showDiff(diff,options) {
if (diffVisible) {
return;
}
options = options || {};
var mode = options.mode || 'merge';
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@ -1065,15 +1234,56 @@ RED.diff = (function() {
currentDiff = diff;
var trayOptions = {
title: "Review Changes", //TODO: nls
title: options.title||"Review Changes", //TODO: nls
width: Infinity,
overlay: true,
buttons: [
{
text: RED._("common.label.cancel"),
text: RED._((options.mode === 'merge')?"common.label.cancel":"common.label.close"),
click: function() {
RED.tray.close();
}
},
}
],
resize: function(dimensions) {
// trayWidth = dimensions.width;
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var diffTable = buildDiffPanel(trayBody,diff,options);
diffTable.list.hide();
if (remoteDiff) {
$("#node-diff-view-diff-merge").show();
if (Object.keys(conflicts).length === 0) {
$("#node-diff-view-diff-merge").removeClass('disabled');
} else {
$("#node-diff-view-diff-merge").addClass('disabled');
}
} else {
$("#node-diff-view-diff-merge").hide();
}
refreshConflictHeader();
// console.log("--------------");
// console.log(localDiff);
// console.log(remoteDiff);
setTimeout(function() {
diffTable.finish();
diffTable.list.show();
},300);
$("#sidebar-shade").show();
},
close: function() {
diffVisible = false;
$("#sidebar-shade").hide();
},
show: function() {
}
}
if (options.mode === 'merge') {
trayOptions.buttons.push(
{
id: "node-diff-view-diff-merge",
text: RED._("deploy.confirm.button.merge"),
@ -1086,189 +1296,9 @@ RED.diff = (function() {
}
}
}
],
resize: function(dimensions) {
// trayWidth = dimensions.width;
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var diffPanel = buildDiffPanel(trayBody);
if (remoteDiff) {
$("#node-diff-view-diff-merge").show();
if (Object.keys(conflicts).length === 0) {
$("#node-diff-view-diff-merge").removeClass('disabled');
} else {
$("#node-diff-view-diff-merge").addClass('disabled');
}
} else {
$("#node-diff-view-diff-merge").hide();
}
refreshConflictHeader();
$("#node-dialog-view-diff-headers").empty();
// console.log("--------------");
// console.log(localDiff);
// console.log(remoteDiff);
var currentConfig = localDiff.currentConfig;
var newConfig = localDiff.newConfig;
conflicts = conflicts || {};
var el = {
diff: localDiff,
def: {
category: 'config',
color: '#f0f0f0'
},
tab: {
n: {},
nodes: currentConfig.globals
},
newTab: {
n: {},
nodes: newConfig.globals
}
};
if (remoteDiff !== undefined) {
diffPanel.addClass('node-diff-three-way');
$('<div data-i18n="diff.local"></div><div data-i18n="diff.remote"></div>').i18n().appendTo("#node-dialog-view-diff-headers");
el.remoteTab = {
n:{},
nodes:remoteDiff.newConfig.globals
};
el.remoteDiff = remoteDiff;
} else {
diffPanel.removeClass('node-diff-three-way');
}
diffList.editableList('addItem',el);
var seenTabs = {};
currentConfig.tabOrder.forEach(function(tabId) {
var tab = currentConfig.tabs[tabId];
var el = {
diff: localDiff,
def: RED.nodes.getType('tab'),
tab:tab
};
if (newConfig.tabs.hasOwnProperty(tabId)) {
el.newTab = newConfig.tabs[tabId];
}
if (remoteDiff !== undefined) {
el.remoteTab = remoteDiff.newConfig.tabs[tabId];
el.remoteDiff = remoteDiff;
}
seenTabs[tabId] = true;
diffList.editableList('addItem',el)
});
newConfig.tabOrder.forEach(function(tabId) {
if (!seenTabs[tabId]) {
seenTabs[tabId] = true;
var tab = newConfig.tabs[tabId];
var el = {
diff: localDiff,
def: RED.nodes.getType('tab'),
tab:tab,
newTab: tab
};
if (remoteDiff !== undefined) {
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el)
}
});
if (remoteDiff !== undefined) {
remoteDiff.newConfig.tabOrder.forEach(function(tabId) {
if (!seenTabs[tabId]) {
var tab = remoteDiff.newConfig.tabs[tabId];
// TODO how to recognise this is a remotely added flow
var el = {
diff: localDiff,
remoteDiff: remoteDiff,
def: RED.nodes.getType('tab'),
tab:tab,
remoteTab:tab
};
diffList.editableList('addItem',el)
}
});
}
var subflowId;
for (subflowId in currentConfig.subflows) {
if (currentConfig.subflows.hasOwnProperty(subflowId)) {
seenTabs[subflowId] = true;
el = {
diff: localDiff,
def: {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9"
},
tab:currentConfig.subflows[subflowId]
}
if (newConfig.subflows.hasOwnProperty(subflowId)) {
el.newTab = newConfig.subflows[subflowId];
}
if (remoteDiff !== undefined) {
el.remoteTab = remoteDiff.newConfig.subflows[subflowId];
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el)
}
}
for (subflowId in newConfig.subflows) {
if (newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) {
seenTabs[subflowId] = true;
el = {
diff: localDiff,
def: {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9"
},
tab:newConfig.subflows[subflowId],
newTab:newConfig.subflows[subflowId]
}
if (remoteDiff !== undefined) {
el.remoteDiff = remoteDiff;
}
diffList.editableList('addItem',el)
}
}
if (remoteDiff !== undefined) {
for (subflowId in remoteDiff.newConfig.subflows) {
if (remoteDiff.newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) {
el = {
diff: localDiff,
remoteDiff: remoteDiff,
def: {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9"
},
tab:remoteDiff.newConfig.subflows[subflowId],
remoteTab: remoteDiff.newConfig.subflows[subflowId]
}
diffList.editableList('addItem',el)
}
}
}
$("#sidebar-shade").show();
},
close: function() {
diffVisible = false;
$("#sidebar-shade").hide();
},
show: function() {
}
);
}
RED.tray.show(trayOptions);
}
@ -1374,7 +1404,7 @@ RED.diff = (function() {
overlay: true,
buttons: [
{
text: RED._("common.label.done"),
text: RED._("common.label.close"),
click: function() {
RED.tray.close();
}
@ -1646,7 +1676,7 @@ RED.diff = (function() {
return string1 === string2 ? 0 : 1;
}
function createUnifiedDiffTable(files) {
function createUnifiedDiffTable(files,commitOptions) {
var diffPanel = $('<div></div>');
files.forEach(function(file) {
var hunks = file.hunks;
@ -1655,10 +1685,61 @@ RED.diff = (function() {
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable);
var diffRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var diffFileRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffFileRow);
var chevron = $('<i class="node-diff-chevron fa fa-angle-down"></i>').appendTo(content);
diffFileRow.click(function(e) {
diffFileRow.toggleClass("collapsed");
var isCollapsed = diffFileRow.hasClass("collapsed");
diffFileRow.nextUntil(".node-text-diff-file-header").toggle(!isCollapsed);
})
var label = $('<span></span>').text(file.file).appendTo(content);
if (commitOptions.project.files && commitOptions.project.files.flow === file.file) {
var tools = $('<span style="float: right;" class="button-group"></span>').appendTo(content);
$('<button class="editor-button editor-button-small">show flow diff</button>').appendTo(tools).click(function(e) {
e.preventDefault();
e.stopPropagation();
var projectName = commitOptions.project.name;
var filename = commitOptions.project.files.flow;
var oldVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.oldRev+"/"+filename;
var newVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.newRev+"/"+filename;
$.when($.getJSON(oldVersionUrl),$.getJSON(newVersionUrl)).done(function(oldVersion,newVersion) {
var oldFlow;
var newFlow;
try {
oldFlow = JSON.parse(oldVersion[0].content||"[]");
} catch(err) {
console.log("Old Version doesn't contain valid JSON:",oldVersionUrl);
console.log(err);
return;
}
try {
newFlow = JSON.parse(newVersion[0].content||"[]");
} catch(err) {
console.log("New Version doesn't contain valid JSON:",newFlow);
console.log(err);
return;
}
var localDiff = generateDiff(oldFlow,oldFlow);
var remoteDiff = generateDiff(oldFlow,newFlow);
var diff = resolveDiffs(localDiff,remoteDiff);
showDiff(diff,{
title: filename,
mode: 'view',
oldRevTitle: commitOptions.oldRevTitle,
newRevTitle: commitOptions.newRevTitle
});
// var flowDiffRow = $("<tr>").insertAfter(diffRow);
// var content = $('<td colspan="3"></td>').appendTo(flowDiffRow);
// currentDiff = diff;
// var diffTable = buildDiffPanel(content,diff,{mode:"view"}).finish();
});
})
}
for (var i=0;i<hunks.length;i++) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
@ -1681,9 +1762,13 @@ RED.diff = (function() {
$('<span class="prefix">').text(lineText[0]).appendTo(line);
$('<span>').text(lineText.substring(1)).appendTo(line);
if (lineText[0] === '+') {
localLineNo.addClass("added");
remoteLineNo.addClass("added");
line.addClass("added");
remoteLineNo.text(remoteLine++);
} else if (lineText[0] === '-') {
localLineNo.addClass("removed");
remoteLineNo.addClass("removed");
line.addClass("removed");
localLineNo.text(localLine++);
} else {
@ -1701,15 +1786,15 @@ RED.diff = (function() {
return diffPanel;
}
function showCommitDiff(diff,title) {
var commit = parseCommitDiff(diff);
function showCommitDiff(options) {
var commit = parseCommitDiff(options.commit);
var trayOptions = {
title: title||"Compare Changes", //TODO: nls
title: "View Commit Changes", //TODO: nls
width: Infinity,
overlay: true,
buttons: [
{
text: RED._("common.label.done"),
text: RED._("common.label.close"),
click: function() {
RED.tray.close();
}
@ -1726,12 +1811,17 @@ RED.diff = (function() {
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable);
var diffRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
var diffRow = $('<tr class="node-text-diff-commit-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<pre></pre>').text(commit.preamble).appendTo(content);
$("<h3>").text(commit.title).appendTo(content);
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
var summary = $('<div class="commit-summary"></div>').appendTo(content);
$('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
createUnifiedDiffTable(commit.files).appendTo(diffPanel);
createUnifiedDiffTable(commit.files,options).appendTo(diffPanel);
},
@ -1744,7 +1834,10 @@ RED.diff = (function() {
}
RED.tray.show(trayOptions);
}
function showUnifiedDiff(diff,title) {
function showUnifiedDiff(options) {
var diff = options.diff;
var title = options.title;
var files = parseUnifiedDiff(diff);
var trayOptions = {
title: title||"Compare Changes", //TODO: nls
@ -1752,7 +1845,7 @@ RED.diff = (function() {
overlay: true,
buttons: [
{
text: RED._("common.label.done"),
text: RED._("common.label.close"),
click: function() {
RED.tray.close();
}
@ -1764,7 +1857,7 @@ RED.diff = (function() {
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
createUnifiedDiffTable(files).appendTo(diffPanel);
createUnifiedDiffTable(files,options).appendTo(diffPanel);
},
@ -1779,16 +1872,35 @@ RED.diff = (function() {
}
function parseCommitDiff(diff) {
var result = {
};
var result = {};
var lines = diff.split("\n");
var comment = [];
for (var i=0;i<lines.length;i++) {
if (/^diff /.test(lines[i])) {
if (/^commit /.test(lines[i])) {
result.sha = lines[i].substring(7);
} else if (/^Author: /.test(lines[i])) {
result.author = lines[i].substring(8);
var m = /^(.*) <(.*)>$/.exec(result.author);
if (m) {
result.authorName = m[1];
result.authorEmail = m[2];
}
} else if (/^Date: /.test(lines[i])) {
result.date = lines[i].substring(8);
} else if (/^ /.test(lines[i])) {
if (!result.title) {
result.title = lines[i].substring(4);
} else {
if (lines[i].length !== 4 || comment.length > 0) {
comment.push(lines[i].substring(4));
}
}
} else if (/^diff /.test(lines[i])) {
result.files = parseUnifiedDiff(lines.slice(i));
break;
}
}
result.preamble = lines.slice(0,i).join("\n");
result.comment = comment.join("\n");
return result;
}
function parseUnifiedDiff(diff) {

View File

@ -489,9 +489,10 @@ RED.editor = (function() {
function getEditStackTitle() {
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 label = node.type;
label = node.type;
if (node.type === '_expression') {
label = RED._("expressionEditor.title");
} else if (node.type === '_json') {
@ -524,7 +525,7 @@ RED.editor = (function() {
title += '<li>'+label+'</li>';
}
title += '</ul>';
return title;
return label;
}
function buildEditForm(container,formId,type,ns) {

View File

@ -908,9 +908,6 @@ RED.projects.settings = (function() {
},payload).always(function() {
RED.deploy.setDeployInflight(false);
});
});
var updateForm = function() {
if (activeProject.settings.credentialSecretInvalid) {

View File

@ -44,11 +44,13 @@ RED.projects = (function() {
},
'create': (function() {
var projectNameInput;
var projectSummaryEditor;
var projectSummaryInput;
var projectFlowFileInput;
var projectSecretInput;
var projectSecretSelect;
var copyProject;
var projectRepoInput;
var emptyProjectCredentialInput;
return {
title: "Create a new project", // TODO: NLS
@ -84,6 +86,16 @@ RED.projects = (function() {
projectRepoInput.removeClass("input-error");
}
} else if (projectType === 'empty') {
projectFlowFileInput.toggleClass("input-error",projectFlowFileInput.val()==='')
valid = valid && projectFlowFileInput.val()!=='';
var encryptionState = $("input[name=projects-encryption-type]:checked").val();
if (encryptionState === 'enabled') {
var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val();
if (encryptionKeyType === 'custom') {
valid = valid && emptyProjectCredentialInput.val()!==''
}
}
}
$("#projects-dialog-create").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
@ -108,12 +120,79 @@ RED.projects = (function() {
projectNameInput = $('<input type="text"></input>').appendTo(row);
var projectNameInputChanged = false;
projectNameInput.on("change keyup paste",function() { validateForm(); });
projectNameInput.on("change keyup paste",function() { projectNameInputChanged = true; validateForm(); });
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row);
// Empty Project
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Summary <small>(optional)</small></label>').appendTo(row);
projectSummaryEditor = $('<input type="text">').appendTo(row);
$('<label>Description</label>').appendTo(row);
projectSummaryInput = $('<input type="text">').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Flow file</label>').appendTo(row);
projectFlowFileInput = $('<input type="text">').val("flow.json")
.on("change keyup paste",validateForm)
.appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Credentials</label>').appendTo(row);
var credentialsBox = $('<div style="width: 550px">').appendTo(row);
var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox);
var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox);
var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" checked style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">Enable encryption</span></label>').appendTo(credentialsEnabledBox);
var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">Disable encryption</span></label>').appendTo(credentialsDisabledBox);
credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) {
var val = $(this).val();
var toEnable;
var toDisable;
if (val === 'enabled') {
toEnable = credentialsEnabledBox;
toDisable = credentialsDisabledBox;
$(".projects-encryption-enabled-row").show();
$(".projects-encryption-disabled-row").hide();
} else {
toDisable = credentialsEnabledBox;
toEnable = credentialsDisabledBox;
$(".projects-encryption-enabled-row").hide();
$(".projects-encryption-disabled-row").show();
}
toEnable.css({
borderColor: "#ccc",
borderRightColor: "white"
});
toDisable.css({
borderColor: "white",
borderRightColor: "#ccc"
})
validateForm();
})
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" checked style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="default" name="projects-encryption-key"> <span style="vertical-align: middle;">Use default key</span></label>').appendTo(row);
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="custom" name="projects-encryption-key"> <span style="vertical-align: middle;">Use custom key</span></label>').appendTo(row);
row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
emptyProjectCredentialInput = $('<input disabled type="password" style="margin-left: 25px; width: calc(100% - 30px);"></input>').appendTo(row);
emptyProjectCredentialInput.on("change keyup paste", validateForm);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
$('<div class="form-tips form-warning" style="padding: 15px; margin: 5px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row);
credentialsRightBox.find("input[name=projects-encryption-key]").click(function() {
var val = $(this).val();
emptyProjectCredentialInput.attr("disabled",val === 'default');
validateForm();
})
// Copy Project
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-copy"></div>').appendTo(container);
@ -151,11 +230,13 @@ RED.projects = (function() {
validateForm();
});
// Secret - empty/clone
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>Credentials key</label>').appendTo(row);
// Secret - clone
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>Credentials encryption key</label>').appendTo(row);
projectSecretInput = $('<input type="text"></input>').appendTo(row);
createAsEmpty.click();
return container;
},
buttons: [
@ -177,8 +258,25 @@ RED.projects = (function() {
name: projectNameInput.val(),
}
if (projectType === 'empty') {
projectData.summary = projectSummaryEditor.val();
projectData.credentialSecret = projectSecretInput.val();
projectData.summary = projectSummaryInput.val();
projectData.files = {
flow: projectFlowFileInput.val()
};
var encryptionState = $("input[name=projects-encryption-type]:checked").val();
if (encryptionState === 'enabled') {
var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val();
if (encryptionKeyType === 'custom') {
projectData.credentialSecret = emptyProjectCredentialInput.val();
} else {
// If 'use default', leave projectData.credentialSecret blank - as that will trigger
// it to use the default (TODO: if its set...)
}
} else {
// Disabled encryption by explicitly setting credSec to false
projectData.credentialSecret = false;
}
} else if (projectType === 'copy') {
projectData.copy = copyProject.name;
} else if (projectType === 'clone') {

View File

@ -65,7 +65,16 @@ RED.sidebar.versionControl = (function() {
// done(error,null);
},
200: function(data) {
RED.diff.showUnifiedDiff(data.diff,(unstaged?"Unstaged":"Staged")+" changes : "+entry.file);
var options = {
diff: data.diff,
title: (unstaged?"Unstaged":"Staged")+" changes : "+entry.file,
oldRevTitle: unstaged?(entry.indexStatus === " "?"HEAD":"Staged"):"HEAD",
newRevTitle: unstaged?"Unstaged":"Staged",
oldRev: unstaged?(entry.indexStatus === " "?"@":":0"):"@",
newRev: unstaged?"_":":0",
project: activeProject
}
RED.diff.showUnifiedDiff(options);
// console.log(data.diff);
},
400: {
@ -336,7 +345,13 @@ RED.sidebar.versionControl = (function() {
var activeProject = RED.projects.getActiveProject();
if (activeProject) {
$.getJSON("/projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
RED.diff.showCommitDiff(result.commit);
result.project = activeProject;
result.oldRev = entry.sha+"~1";
result.newRev = entry.sha;
result.oldRevTitle = "Commit "+entry.sha.substring(0,7)+"~1";
result.newRevTitle = "Commit "+entry.sha.substring(0,7);
result.date = humanizeSinceDate(parseInt(entry.date));
RED.diff.showCommitDiff(result);
});
}
});

View File

@ -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 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) {
$('<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) {
options.maximized = true;

View File

@ -15,15 +15,15 @@
**/
#node-dialog-view-diff {
.node-dialog-view-diff-panel {
.red-ui-editableList-container {
border-radius:1px;
padding:0;
background: #f9f9f9;
}
#node-dialog-view-diff-diff {
.node-dialog-view-diff-diff {
position: absolute;
top:80px;
top:30px;
bottom:10px;
left:10px;
right:10px;
@ -38,12 +38,22 @@
padding: 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;
left:237px;
right:18px;
top: 55px;
top: 5px;
height: 25px;
div {
height: 25px;
@ -553,7 +563,6 @@
margin: 10px;
border: 1px solid $secondary-border-color;
border-radius: 3px;
font-family: monospace;
table-layout: fixed;
width: calc(100% - 20px);
}
@ -562,6 +571,7 @@
word-wrap: break-word;
}
td.lineno {
font-family: monospace;
text-align: right;
color: #999;
background: #fafafa;
@ -571,6 +581,7 @@
border-left: 1px solid $secondary-border-color;
}
td.linetext {
font-family: monospace;
white-space: pre-wrap;
padding: 1px 5px;
span.prefix {
@ -602,12 +613,42 @@
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-file-header td {
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;

View File

@ -23,10 +23,23 @@
.projects-edit-form form {
margin: 0;
.form-row {
margin-bottom: 20px;
margin-bottom: 15px;
label {
width: auto;
color: #555;
width: 100%;
display: block;
font-weight: 500;
&.projects-edit-form-sublabel {
color: #999;
text-align: right;
margin-bottom: -15px;
font-weight: normal;
}
&.projects-edit-form-inline-label {
font-weight: normal;
color: inherit;
width: auto;
}
}
input[type=text], input[type=password],textarea {
width: 100%;
@ -36,6 +49,7 @@
vertical-align: top;
}
}
}
.projects-dialog-spinner {
position: absolute;

View File

@ -129,9 +129,24 @@ module.exports = {
})
});
app.post(/([^\/]+)\/stage\/(.+)$/, function(req,res) {
var projectName = req.params[0];
var file = req.params[1];
// /:project/files/:treeish/file-path
app.get("/:id/files/:treeish/*", function(req,res) {
var projectId = req.params.id;
var treeish = req.params.treeish;
var filePath = req.params[0];
runtime.storage.projects.getFile(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()});
})
});
app.post("/:id/stage/*", function(req,res) {
var projectName = req.params.id;
var file = req.params[0];
runtime.storage.projects.stageFile(projectName,file).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
@ -166,9 +181,9 @@ module.exports = {
})
});
app.delete(/([^\/]+)\/stage\/(.+)$/, function(req,res) {
var projectName = req.params[0];
var file = req.params[1];
app.delete("/:id/stage/*", function(req,res) {
var projectName = req.params.id;
var file = req.params[0];
runtime.storage.projects.unstageFile(projectName,file).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
@ -189,10 +204,10 @@ module.exports = {
})
});
app.get(/([^\/]+)\/diff\/([^\/]+)\/(.+)$/, function(req,res) {
var projectName = req.params[0];
var type = req.params[1];
var file = req.params[2];
app.get("/:id/diff/:type/*", function(req,res) {
var projectName = req.params.id;
var type = req.params.type;
var file = req.params[0];
runtime.storage.projects.getFileDiff(projectName,file,type).then(function(data) {
res.json({
diff: data
@ -229,14 +244,6 @@ module.exports = {
})
});
app.get(new RegExp("/([^\/]+)\/files\/(.*)"), function(req,res) {
// Get project file
});
app.post(new RegExp("/([^\/]+)\/files\/(.*)"), function(req,res) {
// Update project file
});
return app;
}
}

View File

@ -27,11 +27,6 @@ var projects = require("./projects");
var initialFlowLoadComplete = false;
var settings;
var flowsFile;
var flowsFullPath;
var flowsFileBackup;
var credentialsFile;
var credentialsFileBackup;
var localfilesystem = {
init: function(_settings, runtime) {

View File

@ -212,6 +212,13 @@ Project.prototype.getCommits = function(options) {
Project.prototype.getCommit = function(sha) {
return gitTools.getCommit(this.path,sha);
}
Project.prototype.getFile = function (filePath,treeish) {
if (treeish !== "_") {
return gitTools.getFile(this.path, filePath, treeish);
} else {
return fs.readFile(fspath.join(this.path,filePath),"utf8");
}
};
Project.prototype.getFlowFile = function() {
console.log("Project.getFlowFile = ",this.paths.flowFile);
@ -255,7 +262,16 @@ Project.prototype.toJSON = function () {
}
};
function getCredentialsFilename(filename) {
// TODO: DRY - ./index.js
var ffDir = fspath.dirname(filename);
var ffExt = fspath.extname(filename);
var ffBase = fspath.basename(filename,ffExt);
return fspath.join(ffDir,ffBase+"_cred"+ffExt);
}
function getBackupFilename(filename) {
// TODO: DRY - ./index.js
var ffName = fspath.basename(filename);
var ffDir = fspath.dirname(filename);
return fspath.join(ffDir,"."+ffName+".backup");
@ -287,8 +303,23 @@ function createDefaultProject(project) {
promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project)));
}
}
if (project.files) {
if (project.files.flow && !/\.\./.test(project.files.flow)) {
var flowFilePath = fspath.join(projectPath,project.files.flow);
promises.push(util.writeFile(flowFilePath,"[]"));
var credsFilePath = getCredentialsFilename(flowFilePath);
promises.push(util.writeFile(credsFilePath,"{}"));
}
}
return when.all(promises).then(function() {
return gitTools.stageFile(projectPath,Object.keys(defaultFileSet));
var files = Object.keys(defaultFileSet);
if (project.files) {
if (project.files.flow && !/\.\./.test(project.files.flow)) {
files.push(project.files.flow);
files.push(getCredentialsFilename(flowFilePath))
}
}
return gitTools.stageFile(projectPath,files);
}).then(function() {
return gitTools.commit(projectPath,"Create project");
})
@ -339,7 +370,7 @@ function createProject(metadata) {
createProjectDirectory(project).then(function() {
var projects = settings.get('projects');
projects.projects[project] = {};
if (metadata.credentialSecret) {
if (metadata.hasOwnProperty('credentialSecret')) {
projects.projects[project].credentialSecret = metadata.credentialSecret;
}
if (metadata.remote) {

View File

@ -14,20 +14,43 @@
* limitations under the License.
**/
var fspath = require("path");
function getCredentialsFilename(filename) {
// TODO: DRY - ./index.js
var ffDir = fspath.dirname(filename);
var ffExt = fspath.extname(filename);
var ffBase = fspath.basename(filename,ffExt);
return fspath.join(ffDir,ffBase+"_cred"+ffExt);
}
module.exports = {
"package.json": function(project) {
return JSON.stringify({
var package = {
"name": project.name,
"description": project.summary||"A Node-RED Project",
"version": "0.0.1",
"dependencies": {}
},"",4);
"dependencies": {},
"node-red": {
"settings": {
}
}
};
if (project.files) {
if (project.files.flow) {
package['node-red'].settings.flowFile = project.files.flow;
package['node-red'].settings.credentialsFile = getCredentialsFilename(project.files.flow);
}
}
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 "{}" },
"flow.json": function() { return "[]" },
"flow_cred.json": function() { return "{}" },
// "flow.json": function() { return "[]" },
// "flow_cred.json": function() { return "{}" },
".gitignore": function() { return "*.backup" ;}
}

View File

@ -215,6 +215,10 @@ module.exports = {
return runCommand(gitCommand,args,cwd);
},
getFiles: getFiles,
getFile: function(cwd, filePath, treeish) {
var args = ["show",treeish+":"+filePath];
return runCommand(gitCommand,args,cwd);
},
stageFile: function(cwd,file) {
var args = ["add"];
if (Array.isArray(file)) {

View File

@ -153,8 +153,9 @@ function getCommit(project,sha) {
return activeProject.getCommit(sha);
}
function getFile(project,path) {
function getFile(project,filePath,sha) {
checkActiveProject(project);
return activeProject.getFile(filePath,sha);
}
function getActiveProject() {
@ -339,6 +340,7 @@ module.exports = {
createProject: createProject,
updateProject: updateProject,
getFiles: getFiles,
getFile: getFile,
stageFile: stageFile,
unstageFile: unstageFile,
commit: commit,