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

Confirm actions that would overwrite dirty workspace

This commit is contained in:
Nick O'Leary 2017-11-24 23:12:35 +00:00
parent e5ff25b92d
commit 14c48253f6
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
10 changed files with 244 additions and 123 deletions

View File

@ -59,13 +59,22 @@
// handled below // handled below
return; return;
} }
if (notificationId === "project-change") { if (notificationId === "project-update") {
RED.nodes.clear(); RED.nodes.clear();
RED.history.clear(); RED.history.clear();
RED.view.redraw(true); RED.view.redraw(true);
RED.projects.refresh(function() { RED.projects.refresh(function() {
loadFlows(function() { loadFlows(function() {
RED.notify("NLS: Project changed to "+msg.project); console.log(msg);
var project = RED.projects.getActiveProject();
var message = {
"change-branch":"Change to local branch '"+project.branches.local+"'",
"abort-merge":"Git merge aborted",
"loaded":"Project '"+msg.project+"' loaded",
"updated":"Project '"+msg.project+"' updated",
"pull":"Project '"+msg.project+"' reloaded"
}[msg.action]
RED.notify(message);
RED.sidebar.info.refresh() RED.sidebar.info.refresh()
}); });
}); });

View File

@ -224,24 +224,29 @@ 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 = $('<div>').text(RED._('deploy.confirm.backgroundUpdate'));
'<br><br><div class="ui-dialog-buttonset">'+ activeNotifyMessage = RED.notify(message,{
'<button>'+RED._('deploy.confirm.button.ignore')+'</button>'+ fixed: true,
'<button class="primary">'+RED._('deploy.confirm.button.review')+'</button>'+ buttons: [
'</div></div>'); {
$(message.find('button')[0]).click(function(evt) { text: RED._('deploy.confirm.button.ignore'),
evt.preventDefault(); click: function() {
activeNotifyMessage.close(); activeNotifyMessage.close();
activeNotifyMessage = null; activeNotifyMessage = null;
}) }
$(message.find('button')[1]).click(function(evt) { },
evt.preventDefault(); {
activeNotifyMessage.close(); text: RED._('deploy.confirm.button.review'),
var nns = RED.nodes.createCompleteNodeSet(); class: "primary",
resolveConflict(nns,false); click: function() {
activeNotifyMessage = null; activeNotifyMessage.close();
}) var nns = RED.nodes.createCompleteNodeSet();
activeNotifyMessage = RED.notify(message,null,true); resolveConflict(nns,false);
activeNotifyMessage = null;
}
}
]
});
} }
}); });
} }

View File

@ -18,7 +18,7 @@ RED.notify = (function() {
var c = 0; var c = 0;
return function(msg,type,fixed,timeout) { return function(msg,type,fixed,timeout) {
var options = {}; var options = {};
if (typeof type === 'object') { if (type !== null && typeof type === 'object') {
options = type; options = type;
fixed = options.fixed; fixed = options.fixed;
timeout = options.timeout; timeout = options.timeout;
@ -56,6 +56,17 @@ RED.notify = (function() {
} else { } else {
$(n).append(msg); $(n).append(msg);
} }
if (options.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n)
options.buttons.forEach(function(buttonDef) {
var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
if (buttonDef.class) {
b.addClass(buttonDef.class);
}
})
}
$("#notifications").append(n); $("#notifications").append(n);
$(n).slideDown(300); $(n).slideDown(300);
n.close = (function() { n.close = (function() {

View File

@ -315,6 +315,7 @@ RED.projects = (function() {
if (projectName === "" || projectName === autoInsertedName) { if (projectName === "" || projectName === autoInsertedName) {
autoInsertedName = m[1]; autoInsertedName = m[1];
projectNameInput.val(autoInsertedName); projectNameInput.val(autoInsertedName);
projectNameInput.change();
} }
} }
validateForm(); validateForm();
@ -411,6 +412,7 @@ RED.projects = (function() {
sendRequest({ sendRequest({
url: "projects", url: "projects",
type: "POST", type: "POST",
requireCleanWorkspace: true,
handleAuthFail: false, handleAuthFail: false,
responses: { responses: {
200: function(data) { 200: function(data) {
@ -423,6 +425,9 @@ RED.projects = (function() {
'git_error': function(error) { 'git_error': function(error) {
console.log("git error",error); console.log("git error",error);
}, },
'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error");
},
'git_auth_failed': function(error) { 'git_auth_failed': function(error) {
projectRepoUserInput.addClass("input-error"); projectRepoUserInput.addClass("input-error");
projectRepoPasswordInput.addClass("input-error"); projectRepoPasswordInput.addClass("input-error");
@ -539,6 +544,7 @@ RED.projects = (function() {
sendRequest({ sendRequest({
url: "projects/"+name, url: "projects/"+name,
type: "PUT", type: "PUT",
requireCleanWorkspace: true,
responses: { responses: {
200: function(data) { 200: function(data) {
done(null,data); done(null,data);
@ -627,6 +633,48 @@ RED.projects = (function() {
function sendRequest(options,body) { function sendRequest(options,body) {
// dialogBody.hide(); // dialogBody.hide();
console.log(options.url); console.log(options.url);
if (options.requireCleanWorkspace && RED.nodes.dirty()) {
var message = 'You have undeployed changes that will be lost. Do you want to continue?';
var alwaysCallback;
var cleanNotification = RED.notify(message,{
type:"info",
fixed: true,
modal: true,
buttons: [
{
//id: "node-dialog-delete",
//class: 'leftButton',
text: RED._("common.label.cancel"),
click: function() {
cleanNotification.close();
if (options.cancel) {
options.cancel();
}
if (alwaysCallback) {
alwaysCallback();
}
}
},{
text: 'Continue',
click: function() {
cleanNotification.close();
delete options.requireCleanWorkspace;
sendRequest(options,body).always(function() {
if (alwaysCallback) {
alwaysCallback();
}
})
}
}
]
});
return {
always: function(done) { alwaysCallback = done; }
}
}
var start = Date.now(); var start = Date.now();
// TODO: this is specific to the dialog-based requests // TODO: this is specific to the dialog-based requests
$(".projects-dialog-spinner").show(); $(".projects-dialog-spinner").show();
@ -656,61 +704,62 @@ RED.projects = (function() {
'<div class="form-row"><div style="margin-left: 20px;">'+url+'</div></div>'+ '<div class="form-row"><div style="margin-left: 20px;">'+url+'</div></div>'+
'<div class="form-row"><label for="projects-user-auth-username">Username</label><input id="projects-user-auth-username" type="text"></input></div>'+ '<div class="form-row"><label for="projects-user-auth-username">Username</label><input id="projects-user-auth-username" type="text"></input></div>'+
'<div class="form-row"><label for=projects-user-auth-password">Password</label><input id="projects-user-auth-password" type="password"></input></div>'+ '<div class="form-row"><label for=projects-user-auth-password">Password</label><input id="projects-user-auth-password" type="password"></input></div>'+
'<hr>'+
'<div class="ui-dialog-buttonset">'+
'<button>'+RED._("common.label.cancel")+'</button>'+
'<button><i class="fa fa-refresh"></i> Retry</button>'+
'</div>'+
'</div>'); '</div>');
$(message.find('button')[0]).click(function(evt) {
evt.preventDefault();
notification.close();
})
$(message.find('button')[1]).click(function(evt) {
evt.preventDefault();
var username = $('#projects-user-auth-username').val();
var password = $('#projects-user-auth-password').val();
body = body || {};
var done = function(err) {
if (err) {
console.log("Failed to update auth");
console.log(err);
} else {
sendRequest(options,body);
notification.close();
}
}
sendRequest({
url: "projects/"+activeProject.name,
type: "PUT",
responses: {
0: function(error) {
done(error,null);
},
200: function(data) {
done(null,data);
},
400: {
'unexpected_error': function(error) {
done(error,null);
}
},
}
},{
remote: {
origin: {
username: username,
password: password
}
}
});
})
var notification = RED.notify(message,{ var notification = RED.notify(message,{
type:"error", type:"error",
fixed: true, fixed: true,
modal: true modal: true,
buttons: [
{
//id: "node-dialog-delete",
//class: 'leftButton',
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
},{
text: $('<span><i class="fa fa-refresh"></i> Retry</span>'),
click: function() {
var username = $('#projects-user-auth-username').val();
var password = $('#projects-user-auth-password').val();
body = body || {};
var done = function(err) {
if (err) {
console.log("Failed to update auth");
console.log(err);
} else {
sendRequest(options,body);
notification.close();
}
}
sendRequest({
url: "projects/"+activeProject.name,
type: "PUT",
responses: {
0: function(error) {
done(error,null);
},
200: function(data) {
done(null,data);
},
400: {
'unexpected_error': function(error) {
done(error,null);
}
},
}
},{
remote: {
origin: {
username: username,
password: password
}
}
});
}
}
]
}); });
return; return;
} else if (responses[xhr.responseJSON.error]) { } else if (responses[xhr.responseJSON.error]) {
@ -888,7 +937,6 @@ RED.projects = (function() {
$.getJSON("projects",function(data) { $.getJSON("projects",function(data) {
if (data.active) { if (data.active) {
$.getJSON("projects/"+data.active, function(project) { $.getJSON("projects/"+data.active, function(project) {
console.log(project.branches);
activeProject = project; activeProject = project;
// updateProjectSummary(); // updateProjectSummary();
// updateProjectDescription(); // updateProjectDescription();

View File

@ -582,9 +582,14 @@ RED.sidebar.versionControl = (function() {
} }
var spinner = utils.addSpinnerOverlay(localBranchBox); var spinner = utils.addSpinnerOverlay(localBranchBox);
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
RED.deploy.setDeployInflight(true);
utils.sendRequest({ utils.sendRequest({
url: "projects/"+activeProject.name+"/branches", url: "projects/"+activeProject.name+"/branches",
type: "POST", type: "POST",
requireCleanWorkspace: true,
cancel: function() {
spinner.remove();
},
responses: { responses: {
0: function(error) { 0: function(error) {
spinner.remove(); spinner.remove();
@ -606,7 +611,10 @@ RED.sidebar.versionControl = (function() {
} }
}, },
} }
},body); },body).always(function(){
console.log("switch deployinflight to false")
RED.deploy.setDeployInflight(false);
});
} }
}); });
@ -679,6 +687,9 @@ RED.sidebar.versionControl = (function() {
refresh(true); refresh(true);
}, },
400: { 400: {
'git_connection_failed': function(error) {
RED.notify(error.message);
},
'unexpected_error': function(error) { 'unexpected_error': function(error) {
console.log(error); console.log(error);
// done(error,null); // done(error,null);

View File

@ -371,7 +371,6 @@ module.exports = {
res.json(data); res.json(data);
}) })
.catch(function(err) { .catch(function(err) {
console.log(err.stack);
if (err.code) { if (err.code) {
res.status(400).json({error:err.code, message: err.message}); res.status(400).json({error:err.code, message: err.message});
} else { } else {

View File

@ -29,14 +29,8 @@ var log;
var projectsDir; var projectsDir;
var authCache = {}; var authCache = require("./git/authCache");
function getAuth(project,remote) {
if (authCache.hasOwnProperty(project) && authCache[project].hasOwnProperty(remote)) {
return authCache[project][remote];
}
return null;
}
function Project(name) { function Project(name) {
this.name = name; this.name = name;
this.path = fspath.join(projectsDir,name); this.path = fspath.join(projectsDir,name);
@ -179,10 +173,9 @@ Project.prototype.update = function (data) {
this.package.description = data.summary; this.package.description = data.summary;
} }
if (data.hasOwnProperty('remote')) { if (data.hasOwnProperty('remote')) {
authCache[project.name] = authCache[project.name]||{};
for (var remote in data.remote) { for (var remote in data.remote) {
if (data.remote.hasOwnProperty(remote)) { if (data.remote.hasOwnProperty(remote)) {
authCache[project.name][remote] = data.remote[remote]; authCache.set(project.name,remote,data.remote[remote]);
} }
} }
} }
@ -255,7 +248,7 @@ Project.prototype.status = function() {
var fetchPromise; var fetchPromise;
if (this.remote) { if (this.remote) {
fetchPromise = gitTools.fetch(this.path,getAuth(this.name,'origin')); fetchPromise = gitTools.fetch(this.path,authCache.get(this.name,'origin'));
} else { } else {
fetchPromise = when.resolve(); fetchPromise = when.resolve();
} }
@ -281,9 +274,7 @@ Project.prototype.status = function() {
}); });
} }
return fetchPromise.then(completeStatus).catch(function(e) { return fetchPromise.then(completeStatus).catch(function(e) {
if (e.code === 'git_auth_failed') { if (e.code !== 'git_auth_failed') {
console.log("Fetch auth failed");
} else {
console.log("Fetch failed"); console.log("Fetch failed");
console.log(e); console.log(e);
} }
@ -292,17 +283,17 @@ Project.prototype.status = function() {
}; };
Project.prototype.push = function (remoteBranchName,setRemote) { Project.prototype.push = function (remoteBranchName,setRemote) {
return gitTools.push(this.path, remoteBranchName, setRemote, getAuth(this.name,'origin')); return gitTools.push(this.path, remoteBranchName, setRemote, authCache.get(this.name,'origin'));
}; };
Project.prototype.pull = function (remoteBranchName,setRemote) { Project.prototype.pull = function (remoteBranchName,setRemote) {
var self = this; var self = this;
if (setRemote) { if (setRemote) {
return gitTools.setUpstream(this.path, remoteBranchName).then(function() { return gitTools.setUpstream(this.path, remoteBranchName).then(function() {
return gitTools.pull(self.path, null, getAuth(self.name,'origin')); return gitTools.pull(self.path, null, authCache.get(self.name,'origin'));
}) })
} else { } else {
return gitTools.pull(this.path, remoteBranchName, getAuth(this.name,'origin')); return gitTools.pull(this.path, remoteBranchName, authCache.get(this.name,'origin'));
} }
}; };
@ -355,7 +346,7 @@ Project.prototype.getBranches = function (remote) {
var self = this; var self = this;
var fetchPromise; var fetchPromise;
if (remote) { if (remote) {
fetchPromise = gitTools.fetch(this.path,getAuth(this.name,'origin')) fetchPromise = gitTools.fetch(this.path,authCache.get(this.name,'origin'))
} else { } else {
fetchPromise = when.resolve(); fetchPromise = when.resolve();
} }
@ -535,13 +526,12 @@ function createProject(metadata) {
if (metadata.remote) { if (metadata.remote) {
var auth; var auth;
if (metadata.remote.hasOwnProperty("username") && metadata.remote.hasOwnProperty("password")) { if (metadata.remote.hasOwnProperty("username") && metadata.remote.hasOwnProperty("password")) {
authCache[project] = { authCache.set(project,'origin',{ // TODO: hardcoded remote name
origin: { // TODO: hardcoded remote name
username: metadata.remote.username, username: metadata.remote.username,
password: metadata.remote.password password: metadata.remote.password
} }
} );
auth = authCache[project].origin; auth = authCache.get(project,'origin');
} }
return gitTools.clone(metadata.remote,auth,projectPath).then(function(result) { return gitTools.clone(metadata.remote,auth,projectPath).then(function(result) {

View File

@ -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.
**/
var authCache = {}
module.exports = {
clear: function(project,remote) {
if (remote && authCache.hasOwnProperty(project)) {
delete authCache[project][remote];
return;
}
delete authCache[project];
},
set: function(project,remote,auth) {
if (authCache.hasOwnProperty(project)) {
authCache[project][remote] = auth;
} else {
authCache[project] = {
remote: auth
}
}
},
get: function(project,remote) {
if (authCache.hasOwnProperty(project)) {
return authCache[project][remote];
}
return
}
}

View File

@ -1,18 +1,18 @@
/** /**
* Copyright JS Foundation and other contributors, http://js.foundation * Copyright JS Foundation and other contributors, http://js.foundation
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* 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.
**/ **/
var net = require("net"); var net = require("net");
var fs = require("fs-extra"); var fs = require("fs-extra");

View File

@ -147,7 +147,7 @@ function getFileDiff(project,file,type) {
} }
function getCommits(project,options) { function getCommits(project,options) {
checkActiveProject(project); checkActiveProject(project);
return activeProject.getCommits(options); return activeProject.getCommits(options);
} }
function getCommit(project,sha) { function getCommit(project,sha) {
checkActiveProject(project); checkActiveProject(project);
@ -164,7 +164,9 @@ function push(project,remoteBranchName,setRemote) {
} }
function pull(project,remoteBranchName,setRemote) { function pull(project,remoteBranchName,setRemote) {
checkActiveProject(project); checkActiveProject(project);
return activeProject.pull(remoteBranchName,setRemote).then(reloadActiveProject); return activeProject.pull(remoteBranchName,setRemote).then(function() {
return reloadActiveProject("pull");
});
} }
function getStatus(project) { function getStatus(project) {
checkActiveProject(project); checkActiveProject(project);
@ -176,7 +178,9 @@ function resolveMerge(project,file,resolution) {
} }
function abortMerge(project) { function abortMerge(project) {
checkActiveProject(project); checkActiveProject(project);
return activeProject.abortMerge().then(reloadActiveProject); return activeProject.abortMerge().then(function() {
return reloadActiveProject("abort-merge")
});
} }
function getBranches(project,remote) { function getBranches(project,remote) {
checkActiveProject(project); checkActiveProject(project);
@ -184,7 +188,9 @@ function getBranches(project,remote) {
} }
function setBranch(project,branchName,isCreate) { function setBranch(project,branchName,isCreate) {
checkActiveProject(project); checkActiveProject(project);
return activeProject.setBranch(branchName,isCreate).then(reloadActiveProject); return activeProject.setBranch(branchName,isCreate).then(function() {
return reloadActiveProject("change-branch");
});
} }
function getBranchStatus(project,branchName) { function getBranchStatus(project,branchName) {
checkActiveProject(project); checkActiveProject(project);
@ -194,14 +200,14 @@ function getActiveProject() {
return activeProject; return activeProject;
} }
function reloadActiveProject() { function reloadActiveProject(action) {
return runtime.nodes.stopFlows().then(function() { return runtime.nodes.stopFlows().then(function() {
return runtime.nodes.loadFlows(true).then(function() { return runtime.nodes.loadFlows(true).then(function() {
runtime.events.emit("runtime-event",{id:"project-change",payload:{ project: activeProject.name}}); runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
}).catch(function(err) { }).catch(function(err) {
// We're committed to the project change now, so notify editors // We're committed to the project change now, so notify editors
// that it has changed. // that it has changed.
runtime.events.emit("runtime-event",{id:"project-change",payload:{ project: activeProject.name}}); runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
throw err; throw err;
}); });
}); });
@ -224,7 +230,7 @@ function setActiveProject(projectName) {
// console.log("Updated file targets to"); // console.log("Updated file targets to");
// console.log(flowsFullPath) // console.log(flowsFullPath)
// console.log(credentialsFile) // console.log(credentialsFile)
return reloadActiveProject(); return reloadActiveProject("loaded");
}) })
}); });
} }
@ -244,7 +250,7 @@ function updateProject(project,data) {
flowsFileBackup = activeProject.getFlowFileBackup(); flowsFileBackup = activeProject.getFlowFileBackup();
credentialsFile = activeProject.getCredentialsFile(); credentialsFile = activeProject.getCredentialsFile();
credentialsFileBackup = activeProject.getCredentialsFileBackup(); credentialsFileBackup = activeProject.getCredentialsFileBackup();
return reloadActiveProject(); return reloadActiveProject("updated");
} else if (result.credentialSecretChanged) { } else if (result.credentialSecretChanged) {
if (isReset || !wasInvalid) { if (isReset || !wasInvalid) {
if (isReset) { if (isReset) {
@ -255,11 +261,11 @@ function updateProject(project,data) {
.then(runtime.storage.saveCredentials) .then(runtime.storage.saveCredentials)
.then(function() { .then(function() {
if (wasInvalid) { if (wasInvalid) {
return reloadActiveProject(); return reloadActiveProject("updated");
} }
}); });
} else if (wasInvalid) { } else if (wasInvalid) {
return reloadActiveProject(); return reloadActiveProject("updated");
} }
} }
}); });
@ -277,11 +283,11 @@ function setCredentialSecret(data) { //existingSecret,secret) {
.then(runtime.storage.saveCredentials) .then(runtime.storage.saveCredentials)
.then(function() { .then(function() {
if (wasInvalid) { if (wasInvalid) {
return reloadActiveProject(); return reloadActiveProject("updated");
} }
}); });
} else if (wasInvalid) { } else if (wasInvalid) {
return reloadActiveProject(); return reloadActiveProject("updated");
} }
}) })
} }