Skip to content

Commit fe10b86

Browse files
committed
Add Git access feature via SSH and Enhance SSH Key management
1 parent 3a311c9 commit fe10b86

7 files changed

Lines changed: 574 additions & 87 deletions

File tree

editor/js/ui/projectUserSettings.js

Lines changed: 236 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
RED.projects.userSettings = (function() {
1818

19+
var gitconfigContainer;
1920
var gitUsernameInput;
2021
var gitEmailInput;
2122

@@ -24,11 +25,9 @@ RED.projects.userSettings = (function() {
2425
var currentGitSettings = RED.settings.get('git') || {};
2526
currentGitSettings.user = currentGitSettings.user || {};
2627

27-
28-
2928
var title = $('<h3></h3>').text("Committer Details").appendTo(pane);
3029

31-
var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
30+
gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
3231
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text("Leave blank to use system default");
3332

3433
var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
@@ -40,77 +39,245 @@ RED.projects.userSettings = (function() {
4039
$('<label for=""></label>').text('Email').appendTo(row);
4140
gitEmailInput = $('<input type="text">').appendTo(row);
4241
gitEmailInput.val(currentGitSettings.user.email||"");
43-
// var sshkeyTitle = $('<h4></h4>').text("SSH Keys").appendTo(gitconfigContainer);
44-
// var generateSshKeyButton = $('<button class="editor-button editor-button-small" style="float: right;">generate new ssh key</button>')
45-
// .appendTo(sshkeyTitle)
46-
// .click(function(evt) {
47-
// console.log('click generateSshKeyButton');
48-
// });
49-
50-
// row = $('<div class="user-settings-row projects-dialog-remote-list"></div>').appendTo(gitconfigContainer);
51-
// var sshkeysList = $('<ol>').appendTo(row);
52-
// sshkeysList.editableList({
53-
// addButton: false,
54-
// height: 'auto',
55-
// addItem: function(outer,index,entry) {
56-
57-
// var header = $('<div class="projects-dialog-remote-list-entry-header"></div>').appendTo(outer);
58-
// entry.header = $('<span>').text(entry.path||"Add new remote").appendTo(header);
59-
// var body = $('<div>').appendTo(outer);
60-
// entry.body = body;
61-
// if (entry.path) {
62-
// entry.removeButton = $('<button class="editor-button editor-button-small projects-dialog-remote-list-entry-delete">remove</button>')
63-
// // .hide()
64-
// .appendTo(header)
65-
// .click(function(e) {
66-
// entry.removed = true;
67-
// body.fadeOut(100);
68-
// entry.header.css("text-decoration","line-through")
69-
// entry.header.css("font-style","italic")
70-
// if (entry.copyToClipboard) {
71-
// entry.copyToClipboard.hide();
72-
// }
73-
// $(this).hide();
74-
// });
75-
// if (entry.data) {
76-
// entry.copyToClipboard = $('<button class="editor-button editor-button-small projects-dialog-remote-list-entry-copy">copy</button>')
77-
// // .hide()
78-
// .appendTo(header)
79-
// .click(function(e) {
80-
// var textarea = document.createElement("textarea");
81-
// textarea.style.position = 'fixed';
82-
// textarea.style.top = 0;
83-
// textarea.style.left = 0;
84-
// textarea.style.width = '2em';
85-
// textarea.style.height = '2em';
86-
// textarea.style.padding = 0;
87-
// textarea.style.border = 'none';
88-
// textarea.style.outline = 'none';
89-
// textarea.style.boxShadow = 'none';
90-
// textarea.style.background = 'transparent';
91-
// textarea.value = entry.data;
92-
// document.body.appendChild(textarea);
93-
// textarea.select();
94-
// try {
95-
// var ret = document.execCommand('copy');
96-
// var msg = ret ? 'successful' : 'unsuccessful';
97-
// console.log('Copy text command was ' + msg);
98-
// } catch (err) {
99-
// console.log('Oops unable to copy');
100-
// }
101-
// document.body.removeChild(textarea);
102-
// });
103-
// }
104-
// }
105-
// }
106-
// });
107-
108-
// var remoteListAddButton = row.find(".red-ui-editableList-addButton").hide();
42+
43+
var sshkeyTitle = $('<h4></h4>').text("SSH Keys").appendTo(gitconfigContainer);
44+
var editSshKeyListButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
45+
.appendTo(sshkeyTitle)
46+
.click(function(evt) {
47+
editSshKeyListButton.hide();
48+
formButtons.show();
49+
sshkeyInputRow.show();
50+
$(".projects-dialog-sshkey-list-button-remove").css('display', 'inline-block');
51+
});
52+
53+
var sshkeyListOptions = {
54+
height: "300px",
55+
deleteAction: function(entry, header) {
56+
sendSSHKeyManagementAPI("DELETE_KEY", entry.name, function(data) {
57+
hideSSHKeyGenerateForm();
58+
utils.refreshSSHKeyList(sshkeysList);
59+
});
60+
},
61+
selectAction: function(entry, header) {
62+
sendSSHKeyManagementAPI("GET_KEY_DETAIL", entry.name, function(data) {
63+
setDialogContext(entry.name, data.publickey);
64+
dialog.dialog("open");
65+
});
66+
}
67+
};
68+
var sshkeysListRow = $('<div class="user-settings-row projects-dialog-sshkeylist"></div>').appendTo(gitconfigContainer);
69+
var sshkeysList = utils.createSSHKeyList(sshkeyListOptions).appendTo(sshkeysListRow);
70+
71+
var sshkeyInputRow = $('<div class="user-settings-row"></div>').hide().appendTo(gitconfigContainer);
72+
var sshkeyNameLabel = $('<label for=""></label>').text('Key Name').appendTo(sshkeyInputRow);
73+
var sshkeyNameInput = $('<input type="text">').appendTo(sshkeyInputRow);
74+
var sshkeyPassphraseLabel = $('<label for=""></label>').text('Passphrase').appendTo(sshkeyInputRow);
75+
var sshkeyPassphraseInput = $('<input type="password">').appendTo(sshkeyInputRow);
76+
var sshkeySamePassphraseLabel = $('<label for=""></label>').text('Same Passphrase').appendTo(sshkeyInputRow);
77+
var sshkeySamePassphraseInput = $('<input type="password">').appendTo(sshkeyInputRow);
78+
79+
var formButtonArea = $('<div style="width: 100%; height: 35px;"></div>').appendTo(gitconfigContainer);
80+
var formButtons = $('<span class="button-group" style="position: absolute; right: 0px; margin-right: 0px;"></span>')
81+
.hide().appendTo(formButtonArea);
82+
83+
function hideSSHKeyGenerateForm() {
84+
editSshKeyListButton.show();
85+
formButtons.hide();
86+
sshkeyInputRow.hide();
87+
sshkeyNameInput.val("");
88+
sshkeyPassphraseInput.val("");
89+
sshkeySamePassphraseInput.val("");
90+
if ( sshkeyNameInput.hasClass('input-error') ) {
91+
sshkeyNameInput.removeClass('input-error');
92+
}
93+
if ( sshkeyPassphraseInput.hasClass('input-error') ) {
94+
sshkeyPassphraseInput.removeClass('input-error');
95+
}
96+
if ( sshkeySamePassphraseInput.hasClass('input-error') ) {
97+
sshkeySamePassphraseInput.removeClass('input-error');
98+
}
99+
$(".projects-dialog-sshkey-list-button-remove").hide();
100+
}
101+
102+
$('<button class="editor-button">Cancel</button>')
103+
.appendTo(formButtons)
104+
.click(function(evt) {
105+
evt.preventDefault();
106+
hideSSHKeyGenerateForm();
107+
});
108+
var generateButton = $('<button class="editor-button">Generate</button>')
109+
.appendTo(formButtons)
110+
.click(function(evt) {
111+
evt.preventDefault();
112+
if ( sshkeyNameInput.hasClass('input-error') ) {
113+
sshkeyNameInput.removeClass('input-error');
114+
}
115+
if ( sshkeyPassphraseInput.hasClass('input-error') ) {
116+
sshkeyPassphraseInput.removeClass('input-error');
117+
}
118+
if ( sshkeySamePassphraseInput.hasClass('input-error') ) {
119+
sshkeySamePassphraseInput.removeClass('input-error');
120+
}
121+
var valid = true;
122+
if ( sshkeyNameInput.val() === "" ) {
123+
sshkeyNameInput.addClass('input-error');
124+
valid = false;
125+
}
126+
if ( sshkeyPassphraseInput.val() !== sshkeySamePassphraseInput.val() ) {
127+
sshkeySamePassphraseInput.addClass('input-error');
128+
valid = false;
129+
}
130+
if ( valid ) {
131+
sendSSHKeyManagementAPI("GENERATE_KEY",
132+
{
133+
name: sshkeyNameInput.val(),
134+
email: gitEmailInput.val(),
135+
password: sshkeyPassphraseInput.val(),
136+
size: 4096
137+
},
138+
function() {
139+
hideSSHKeyGenerateForm();
140+
utils.refreshSSHKeyList(sshkeysList);
141+
},
142+
function(err) {
143+
console.log('err message:', err.message);
144+
if ( err.message.includes('Some SSH Keyfile exists') ) {
145+
sshkeyNameInput.addClass('input-error');
146+
}
147+
else if ( err.message.includes('Failed to generate ssh key files') ) {
148+
sshkeyPassphraseInput.addClass('input-error');
149+
sshkeySamePassphraseInput.addClass('input-error');
150+
}
151+
}
152+
);
153+
}
154+
});
155+
}
156+
157+
function sendSSHKeyManagementAPI(type, param, successCallback, failCallback) {
158+
var url;
159+
var method;
160+
var payload;
161+
switch(type) {
162+
case 'GET_KEY_LIST':
163+
method = 'GET';
164+
url = "settings/user/keys";
165+
break;
166+
case 'GET_KEY_DETAIL':
167+
method = 'GET';
168+
url = "settings/user/keys/" + param;
169+
break;
170+
case 'GENERATE_KEY':
171+
method = 'POST';
172+
url = "settings/user/keys";
173+
payload= param;
174+
break;
175+
case 'DELETE_KEY':
176+
method = 'DELETE';
177+
url = "settings/user/keys/" + param;
178+
break;
179+
default:
180+
console.error('Unexpected type....');
181+
return;
182+
}
183+
var spinner = utils.addSpinnerOverlay(gitconfigContainer);
184+
185+
var done = function(err) {
186+
spinner.remove();
187+
if (err) {
188+
console.log(err);
189+
return;
190+
}
191+
};
192+
193+
console.log('method:', method);
194+
console.log('url:', url);
195+
196+
utils.sendRequest({
197+
url: url,
198+
type: method,
199+
responses: {
200+
0: function(error) {
201+
if ( failCallback ) {
202+
failCallback(error);
203+
}
204+
done(error);
205+
},
206+
200: function(data) {
207+
if ( successCallback ) {
208+
successCallback(data);
209+
}
210+
done();
211+
},
212+
400: {
213+
'unexpected_error': function(error) {
214+
console.log(error);
215+
if ( failCallback ) {
216+
failCallback(error);
217+
}
218+
done(error);
219+
}
220+
},
221+
}
222+
},payload);
223+
}
224+
225+
var dialog;
226+
var dialogBody;
227+
function createPublicKeyDialog() {
228+
dialog = $('<div id="projects-dialog" class="hide node-red-dialog projects-edit-form"><form class="form-horizontal"></form></div>')
229+
.appendTo("body")
230+
.dialog({
231+
modal: true,
232+
autoOpen: false,
233+
width: 600,
234+
resize: false,
235+
open: function(e) {
236+
$(this).parent().find(".ui-dialog-titlebar-close").hide();
237+
},
238+
close: function(e) {
239+
240+
}
241+
});
242+
dialogBody = dialog.find("form");
243+
dialog.dialog('option', 'title', 'SSH public key');
244+
dialog.dialog('option', 'buttons', [
245+
{
246+
text: RED._("common.label.close"),
247+
click: function() {
248+
$( this ).dialog( "close" );
249+
}
250+
},
251+
{
252+
text: "Copy to Clipboard",
253+
click: function() {
254+
var target = document.getElementById('public-key-data');
255+
document.getSelection().selectAllChildren(target);
256+
var ret = document.execCommand('copy');
257+
var msg = ret ? 'successful' : 'unsuccessful';
258+
console.log('Copy text command was ' + msg);
259+
$( this ).dialog("close");
260+
}
261+
}
262+
]);
263+
dialog.dialog({position: { 'my': 'center', 'at': 'center', 'of': window }});
264+
var container = $('<div class="projects-dialog-screen-start"></div>');
265+
$('<div class="projects-dialog-ssh-public-key-name"></div>').appendTo(container);
266+
$('<div class="projects-dialog-ssh-public-key"><pre id="public-key-data"></pre></div>').appendTo(container);
267+
dialogBody.append(container);
268+
}
269+
270+
function setDialogContext(name, data) {
271+
var title = dialog.find("div.projects-dialog-ssh-public-key-name");
272+
title.text(name);
273+
var context = dialog.find("div.projects-dialog-ssh-public-key>pre");
274+
context.text(data);
109275
}
110276

111277
function createSettingsPane(activeProject) {
112278
var pane = $('<div id="user-settings-tab-gitconfig" class="project-settings-tab-pane node-help"></div>');
113279
createRemoteRepositorySection(pane);
280+
createPublicKeyDialog();
114281
return pane;
115282
}
116283

0 commit comments

Comments
 (0)