mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
1965 lines
110 KiB
JavaScript
1965 lines
110 KiB
JavaScript
/**
|
|
* 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 = (function() {
|
|
|
|
var dialog;
|
|
var dialogBody;
|
|
|
|
var activeProject;
|
|
function reportUnexpectedError(error) {
|
|
var notification;
|
|
if (error.error === 'git_missing_user') {
|
|
notification = RED.notify("<p>You Git client is not configured with a username/email.</p>",{
|
|
fixed: true,
|
|
type:'error',
|
|
buttons: [
|
|
{
|
|
text: "Cancel",
|
|
click: function() {
|
|
notification.close();
|
|
}
|
|
},
|
|
{
|
|
text: "Configure Git client",
|
|
click: function() {
|
|
RED.userSettings.show('gitconfig');
|
|
notification.close();
|
|
}
|
|
}
|
|
]
|
|
})
|
|
} else {
|
|
console.log(error);
|
|
notification = RED.notify("<p>An unexpected error occurred:</p><p>"+error.message+"</p><small>code: "+error.error+"</small>",{
|
|
fixed: true,
|
|
modal: true,
|
|
type: 'error',
|
|
buttons: [
|
|
{
|
|
text: "Close",
|
|
click: function() {
|
|
notification.close();
|
|
}
|
|
}
|
|
]
|
|
})
|
|
}
|
|
}
|
|
var screens = {};
|
|
function initScreens() {
|
|
var migrateProjectHeader = $('<div class="projects-dialog-screen-start-hero"></div>');
|
|
$('<span><i class="fa fa-files-o fa-2x"></i> <i class="fa fa-long-arrow-right fa-2x"></i> <i class="fa fa-archive fa-2x"></i></span>').appendTo(migrateProjectHeader)
|
|
$('<hr>').appendTo(migrateProjectHeader);
|
|
|
|
var createProjectOptions = {};
|
|
|
|
screens = {
|
|
'welcome': {
|
|
content: function(options) {
|
|
|
|
var container = $('<div class="projects-dialog-screen-start"></div>');
|
|
|
|
migrateProjectHeader.appendTo(container);
|
|
|
|
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
|
|
$('<p>').text("Hello! We have introduced 'projects' to Node-RED.").appendTo(body);
|
|
$('<p>').text("This is a new way for you to manage your flow files and includes version control of your flows.").appendTo(body);
|
|
$('<p>').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body);
|
|
$('<p>').text("If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.").appendTo(body);
|
|
|
|
return container;
|
|
},
|
|
buttons: [
|
|
{
|
|
// id: "clipboard-dialog-cancel",
|
|
text: "Not right now",
|
|
click: function() {
|
|
createProjectOptions = {};
|
|
$( this ).dialog( "close" );
|
|
}
|
|
},
|
|
{
|
|
text: "Create your first project", // TODO: nls
|
|
class: "primary",
|
|
click: function() {
|
|
show('git-config');
|
|
}
|
|
}
|
|
]
|
|
},
|
|
'git-config': (function() {
|
|
var gitUsernameInput;
|
|
var gitEmailInput;
|
|
return {
|
|
content: function(options) {
|
|
var isGlobalConfig = false;
|
|
var existingGitSettings = RED.settings.get('git');
|
|
if (existingGitSettings && existingGitSettings.user) {
|
|
existingGitSettings = existingGitSettings.user;
|
|
} else if (RED.settings.git && RED.settings.git.globalUser) {
|
|
isGlobalConfig = true;
|
|
existingGitSettings = RED.settings.git.globalUser;
|
|
}
|
|
|
|
var validateForm = function() {
|
|
var name = gitUsernameInput.val().trim();
|
|
var email = gitEmailInput.val().trim();
|
|
var valid = name.length > 0 && email.length > 0;
|
|
$("#projects-dialog-git-config").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
|
|
|
|
}
|
|
|
|
var container = $('<div class="projects-dialog-screen-start"></div>');
|
|
migrateProjectHeader.appendTo(container);
|
|
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
|
|
|
|
$('<p>').text("Setup your version control client").appendTo(body);
|
|
$('<p>').text("Node-RED uses the open source tool Git for version control. It tracks changes to your project files and lets you push them to remote repositories.").appendTo(body);
|
|
$('<p>').text("When you commit a set of changes, Git records who made the changes with a username and email address. The Username can be anything you want - it does not need to be your real name.").appendTo(body);
|
|
|
|
if (isGlobalConfig) {
|
|
$('<p>').text("Your Git client is already configured with the details below.").appendTo(body);
|
|
}
|
|
$('<p>').text("You can change these settings later under the 'Git config' tab of the settings dialog.").appendTo(body);
|
|
|
|
var row = $('<div class="form-row"></div>').appendTo(body);
|
|
$('<label for="">Username</label>').appendTo(row);
|
|
gitUsernameInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.name)||"").appendTo(row);
|
|
// $('<div style="position:relative;"></div>').text("This does not need to be your real name").appendTo(row);
|
|
gitUsernameInput.on("change keyup paste",validateForm);
|
|
|
|
row = $('<div class="form-row"></div>').appendTo(body);
|
|
$('<label for="">Email</label>').appendTo(row);
|
|
gitEmailInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.email)||"").appendTo(row);
|
|
gitEmailInput.on("change keyup paste",validateForm);
|
|
// $('<div style="position:relative;"></div>').text("Something something email").appendTo(row);
|
|
setTimeout(function() {
|
|
gitUsernameInput.focus();
|
|
validateForm();
|
|
},50);
|
|
return container;
|
|
},
|
|
buttons: [
|
|
{
|
|
// id: "clipboard-dialog-cancel",
|
|
text: "Back",
|
|
click: function() {
|
|
show('welcome');
|
|
}
|
|
},
|
|
{
|
|
id: "projects-dialog-git-config",
|
|
text: "Next", // TODO: nls
|
|
class: "primary",
|
|
click: 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);
|
|
show('project-details');
|
|
}
|
|
}
|
|
]
|
|
};
|
|
})(),
|
|
'project-details': (function() {
|
|
var projectNameInput;
|
|
var projectSummaryInput;
|
|
return {
|
|
content: function(options) {
|
|
var projectList = null;
|
|
var projectNameValid;
|
|
|
|
var pendingFormValidation = false;
|
|
$.getJSON("projects", function(data) {
|
|
projectList = {};
|
|
data.projects.forEach(function(p) {
|
|
projectList[p] = true;
|
|
if (pendingFormValidation) {
|
|
pendingFormValidation = false;
|
|
validateForm();
|
|
}
|
|
})
|
|
});
|
|
var container = $('<div class="projects-dialog-screen-start"></div>');
|
|
migrateProjectHeader.appendTo(container);
|
|
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
|
|
|
|
$('<p>').text("Create your project").appendTo(body);
|
|
$('<p>').text("A project is maintained as a Git repository. It makes it much easier to share your flows with others and to collaborate on them.").appendTo(body);
|
|
$('<p>').text("You can create multiple projects and quickly switch between them from the editor.").appendTo(body);
|
|
$('<p>').text("To begin, your project needs a name and an optional description.").appendTo(body);
|
|
|
|
var validateForm = function() {
|
|
var projectName = projectNameInput.val();
|
|
var valid = true;
|
|
if (projectNameInputChanged) {
|
|
if (projectList === null) {
|
|
pendingFormValidation = true;
|
|
return;
|
|
}
|
|
projectNameStatus.empty();
|
|
if (!/^[a-zA-Z0-9\-_]+$/.test(projectName) || projectList[projectName]) {
|
|
projectNameInput.addClass("input-error");
|
|
$('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>').appendTo(projectNameStatus);
|
|
projectNameValid = false;
|
|
valid = false;
|
|
if (projectList[projectName]) {
|
|
projectNameSublabel.text("Project already exists");
|
|
} else {
|
|
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
|
|
}
|
|
} else {
|
|
projectNameInput.removeClass("input-error");
|
|
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
|
|
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
|
|
projectNameValid = true;
|
|
}
|
|
projectNameLastChecked = projectName;
|
|
}
|
|
valid = projectNameValid;
|
|
$("#projects-dialog-create-name").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
|
|
}
|
|
|
|
var row = $('<div class="form-row"></div>').appendTo(body);
|
|
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
|
|
|
|
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
|
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').val(createProjectOptions.name||"").appendTo(subrow);
|
|
var projectNameStatus = $('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
|
|
|
var projectNameInputChanged = false;
|
|
var projectNameLastChecked = "";
|
|
var projectNameValid;
|
|
var checkProjectName;
|
|
var autoInsertedName = "";
|
|
|
|
|
|
projectNameInput.on("change keyup paste",function() {
|
|
projectNameInputChanged = (projectNameInput.val() !== projectNameLastChecked);
|
|
if (checkProjectName) {
|
|
clearTimeout(checkProjectName);
|
|
} else if (projectNameInputChanged) {
|
|
projectNameStatus.empty();
|
|
$('<img src="red/images/spin.svg"/>').appendTo(projectNameStatus);
|
|
if (projectNameInput.val() === '') {
|
|
validateForm();
|
|
return;
|
|
}
|
|
}
|
|
checkProjectName = setTimeout(function() {
|
|
validateForm();
|
|
checkProjectName = null;
|
|
},300)
|
|
});
|
|
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
|
|
|
|
// Empty Project
|
|
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body);
|
|
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row);
|
|
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').val(createProjectOptions.summary||"").appendTo(row);
|
|
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row);
|
|
|
|
setTimeout(function() {
|
|
projectNameInput.focus();
|
|
projectNameInput.change();
|
|
},50);
|
|
return container;
|
|
},
|
|
buttons: function(options) {
|
|
return [
|
|
{
|
|
text: "Back",
|
|
click: function() {
|
|
show('git-config');
|
|
}
|
|
},
|
|
{
|
|
id: "projects-dialog-create-name",
|
|
disabled: true,
|
|
text: "Next", // TODO: nls
|
|
class: "primary disabled",
|
|
click: function() {
|
|
createProjectOptions.name = projectNameInput.val();
|
|
createProjectOptions.summary = projectSummaryInput.val();
|
|
show('default-files', options);
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
})(),
|
|
'default-files': (function() {
|
|
var projectFlowFileInput;
|
|
var projectCredentialFileInput;
|
|
return {
|
|
content: function(options) {
|
|
var container = $('<div class="projects-dialog-screen-start"></div>');
|
|
migrateProjectHeader.appendTo(container);
|
|
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
|
|
|
|
$('<p>').text("Create your project files").appendTo(body);
|
|
$('<p>').text("A project contains your flow files, a README file and a package.json file.").appendTo(body);
|
|
$('<p>').text("It can contain any other files you want to maintain in the Git repository.").appendTo(body);
|
|
if (!options.existingProject && RED.settings.files) {
|
|
$('<p>').text("Your existing flow and credential files will be copied into the project.").appendTo(body);
|
|
}
|
|
|
|
var validateForm = function() {
|
|
var valid = true;
|
|
var flowFile = projectFlowFileInput.val();
|
|
if (flowFile === "" || !/\.json$/.test(flowFile)) {
|
|
valid = false;
|
|
if (!projectFlowFileInput.hasClass("input-error")) {
|
|
projectFlowFileInput.addClass("input-error");
|
|
projectFlowFileInput.next().empty().append('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>');
|
|
}
|
|
projectCredentialFileInput.text("");
|
|
if (!projectCredentialFileInput.hasClass("input-error")) {
|
|
projectCredentialFileInput.addClass("input-error");
|
|
projectCredentialFileInput.next().empty().append('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>');
|
|
}
|
|
} else {
|
|
if (projectFlowFileInput.hasClass("input-error")) {
|
|
projectFlowFileInput.removeClass("input-error");
|
|
projectFlowFileInput.next().empty();
|
|
}
|
|
if (projectCredentialFileInput.hasClass("input-error")) {
|
|
projectCredentialFileInput.removeClass("input-error");
|
|
projectCredentialFileInput.next().empty();
|
|
}
|
|
projectCredentialFileInput.text(flowFile.substring(0,flowFile.length-5)+"_cred.json");
|
|
}
|
|
$("#projects-dialog-create-default-files").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
|
|
}
|
|
var row = $('<div class="form-row"></div>').appendTo(body);
|
|
$('<label for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row);
|
|
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
|
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json";
|
|
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
|
|
.on("change keyup paste",validateForm)
|
|
.appendTo(subrow);
|
|
$('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
|
$('<label class="projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
|
|
|
|
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
|
|
row = $('<div class="form-row"></div>').appendTo(body);
|
|
$('<label for="projects-dialog-screen-create-project-credfile">Credentials file</label>').appendTo(row);
|
|
subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
|
projectCredentialFileInput = $('<div style="width: 100%" class="uneditable-input" id="projects-dialog-screen-create-project-credentials">').text(defaultCredentialsFile)
|
|
.appendTo(subrow);
|
|
$('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
|
|
|
setTimeout(function() {
|
|
projectFlowFileInput.focus();
|
|
validateForm();
|
|
},50);
|
|
|
|
return container;
|
|
},
|
|
buttons: function(options) {
|
|
return [
|
|
{
|
|
// id: "clipboard-dialog-cancel",
|
|
text: options.existingProject?"Cancel":"Back",
|
|
click: function() {
|
|
if (options.existingProject) {
|
|
$(this).dialog('close');
|
|
} else {
|
|
show('project-details',options);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
id: "projects-dialog-create-default-files",
|
|
text: "Next", // TODO: nls
|
|
class: "primary",
|
|
click: function() {
|
|
createProjectOptions.files = {
|
|
flow: projectFlowFileInput.val(),
|
|
credentials: projectCredentialFileInput.text()
|
|
}
|
|
if (!options.existingProject) {
|
|
createProjectOptions.migrateFiles = true;
|
|
}
|
|
show('encryption-config',options);
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
})(),
|
|
'encryption-config': (function() {
|
|
var emptyProjectCredentialInput;
|
|
return {
|
|
content: function(options) {
|
|
|
|
var container = $('<div class="projects-dialog-screen-start"></div>');
|
|
migrateProjectHeader.appendTo(container);
|
|
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
|
|
|
|
$('<p>').text("Setup encryption of your credentials file").appendTo(body);
|
|
if (options.existingProject) {
|
|
$('<p>').text("Your flow credentials file can be encrypted to keep its contents secure.").appendTo(body);
|
|
$('<p>').text("If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.").appendTo(body);
|
|
} else {
|
|
if (RED.settings.flowEncryptionType === 'disabled') {
|
|
$('<p>').text("Your flow credentials file is not currently encrypted.").appendTo(body);
|
|
$('<p>').text("That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.").appendTo(body);
|
|
$('<p>').text("If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.").appendTo(body);
|
|
} else {
|
|
if (RED.settings.flowEncryptionType === 'user') {
|
|
$('<p>').text("Your flow credentials file is currently encrypted using the credentialSecret property from your settings file as the key.").appendTo(body);
|
|
} else if (RED.settings.flowEncryptionType === 'system') {
|
|
$('<p>').text("Your flow credentials file is currently encrypted using a system-generated key. You should provide a new secret key for this project.").appendTo(body);
|
|
}
|
|
$('<p>').text("The key will be stored separately from your project files. You will need to provide the key to use this project in another instance of Node-RED.").appendTo(body);
|
|
}
|
|
}
|
|
|
|
// var row = $('<div class="form-row"></div>').appendTo(body);
|
|
// $('<label for="">Username</label>').appendTo(row);
|
|
// var gitUsernameInput = $('<input type="text">').val(currentGitSettings.user.name||"").appendTo(row);
|
|
// // $('<div style="position:relative;"></div>').text("This does not need to be your real name").appendTo(row);
|
|
//
|
|
// row = $('<div class="form-row"></div>').appendTo(body);
|
|
// $('<label for="">Email</label>').appendTo(row);
|
|
// var gitEmailInput = $('<input type="text">').val(currentGitSettings.user.email||"").appendTo(row);
|
|
// // $('<div style="position:relative;"></div>').text("Something something email").appendTo(row);
|
|
|
|
var validateForm = function() {
|
|
var valid = true;
|
|
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-encryption").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
|
|
}
|
|
|
|
|
|
var row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body);
|
|
$('<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" 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();
|
|
if ($("input[name=projects-encryption-key]:checked").val() === 'custom') {
|
|
emptyProjectCredentialInput.focus();
|
|
}
|
|
|
|
} 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 '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+'" style="margin-left: 5px"><input '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+' type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="default" name="projects-encryption-key"> <span style="vertical-align: middle;">Copy over existing 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="" style="padding: 5px 20px;"><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');
|
|
if (val === "custom") {
|
|
emptyProjectCredentialInput.focus();
|
|
}
|
|
validateForm();
|
|
});
|
|
|
|
setTimeout(function() {
|
|
credentialsLeftBox.find("input[name=projects-encryption-type][value=enabled]").click();
|
|
if (RED.settings.flowEncryptionType !== 'user') {
|
|
credentialsRightBox.find("input[name=projects-encryption-key][value=custom]").click();
|
|
} else {
|
|
credentialsRightBox.find("input[name=projects-encryption-key][value=default]").click();
|
|
}
|
|
validateForm();
|
|
},100);
|
|
|
|
return container;
|
|
},
|
|
buttons: function(options) {
|
|
return [
|
|
{
|
|
// id: "clipboard-dialog-cancel",
|
|
text: "Back",
|
|
click: function() {
|
|
show('default-files',options);
|
|
}
|
|
},
|
|
{
|
|
id: "projects-dialog-create-encryption",
|
|
text: options.existingProject?"Create project files":"Create project", // TODO: nls
|
|
class: "primary disabled",
|
|
disabled: true,
|
|
click: function() {
|
|
var encryptionState = $("input[name=projects-encryption-type]:checked").val();
|
|
if (encryptionState === 'enabled') {
|
|
var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val();
|
|
if (encryptionKeyType === 'custom') {
|
|
createProjectOptions.credentialSecret = emptyProjectCredentialInput.val();
|
|
} else {
|
|
// If 'use existing', leave createProjectOptions.credentialSecret blank
|
|
// - that will trigger it to use the existing key
|
|
// TODO: this option should be disabled if encryption is disabled
|
|
}
|
|
} else {
|
|
// Disabled encryption by explicitly setting credSec to false
|
|
createProjectOptions.credentialSecret = false;
|
|
}
|
|
RED.deploy.setDeployInflight(true);
|
|
RED.projects.settings.switchProject(createProjectOptions.name);
|
|
|
|
var method = "POST";
|
|
var url = "projects";
|
|
|
|
if (options.existingProject) {
|
|
createProjectOptions.initialise = true;
|
|
method = "PUT";
|
|
url = "projects/"+activeProject.name;
|
|
}
|
|
var self = this;
|
|
sendRequest({
|
|
url: url,
|
|
type: method,
|
|
requireCleanWorkspace: true,
|
|
handleAuthFail: false,
|
|
responses: {
|
|
200: function(data) {
|
|
createProjectOptions = {};
|
|
if (options.existingProject) {
|
|
$( self ).dialog( "close" );
|
|
} else {
|
|
show('create-success');
|
|
RED.menu.setDisabled('menu-item-projects-open',false);
|
|
RED.menu.setDisabled('menu-item-projects-settings',false);
|
|
}
|
|
},
|
|
400: {
|
|
'project_exists': function(error) {
|
|
console.log("already exists");
|
|
},
|
|
'git_error': function(error) {
|
|
console.log("git error",error);
|
|
},
|
|
'git_connection_failed': function(error) {
|
|
projectRepoInput.addClass("input-error");
|
|
},
|
|
'git_auth_failed': function(error) {
|
|
projectRepoUserInput.addClass("input-error");
|
|
projectRepoPasswordInput.addClass("input-error");
|
|
// getRepoAuthDetails(req);
|
|
console.log("git auth error",error);
|
|
},
|
|
'*': function(error) {
|
|
reportUnexpectedError(error);
|
|
$( dialog ).dialog( "close" );
|
|
}
|
|
}
|
|
}
|
|
},createProjectOptions).always(function() {
|
|
setTimeout(function() {
|
|
RED.deploy.setDeployInflight(false);
|
|
},500);
|
|
})
|
|
}
|
|
}
|
|
];
|
|
}
|
|
}
|
|
})(),
|
|
'create-success': {
|
|
content: function(options) {
|
|
|
|
var container = $('<div class="projects-dialog-screen-start"></div>');
|
|
migrateProjectHeader.appendTo(container);
|
|
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
|
|
|
|
$('<p>').text("You have successfully created your first project!").appendTo(body);
|
|
$('<p>').text("You can now continue to use Node-RED just as you always have.").appendTo(body);
|
|
$('<p>').text("The 'info' tab in the sidebar shows you what your current active project is. "+
|
|
"The button next to the name can be used to access the project settings view.").appendTo(body);
|
|
$('<p>').text("The 'history' tab in the sidebar can be used to view files that have changed "+
|
|
"in your project and to commit them. It shows you a complete history of your commits and "+
|
|
"allows you to push your changes to a remote repository.").appendTo(body);
|
|
|
|
return container;
|
|
},
|
|
buttons: [
|
|
{
|
|
text: "Done",
|
|
click: function() {
|
|
$( this ).dialog( "close" );
|
|
}
|
|
}
|
|
]
|
|
},
|
|
'create': (function() {
|
|
var projectNameInput;
|
|
var projectSummaryInput;
|
|
var projectFlowFileInput;
|
|
var projectSecretInput;
|
|
var projectSecretSelect;
|
|
var copyProject;
|
|
var projectRepoInput;
|
|
var projectCloneSecret;
|
|
var emptyProjectCredentialInput;
|
|
var projectRepoUserInput;
|
|
var projectRepoPasswordInput;
|
|
var projectNameSublabel;
|
|
var projectRepoSSHKeySelect;
|
|
var projectRepoPassphrase;
|
|
var projectRepoRemoteName
|
|
var projectRepoBranch;
|
|
var selectedProject;
|
|
|
|
return {
|
|
title: "Projects", // TODO: NLS
|
|
content: function(options) {
|
|
var projectList = null;
|
|
selectedProject = null;
|
|
var pendingFormValidation = false;
|
|
$.getJSON("projects", function(data) {
|
|
projectList = {};
|
|
data.projects.forEach(function(p) {
|
|
projectList[p] = true;
|
|
if (pendingFormValidation) {
|
|
pendingFormValidation = false;
|
|
validateForm();
|
|
}
|
|
})
|
|
});
|
|
|
|
var container = $('<div class="projects-dialog-screen-create"></div>');
|
|
var row;
|
|
|
|
var validateForm = function() {
|
|
var projectName = projectNameInput.val();
|
|
var valid = true;
|
|
if (projectNameInputChanged) {
|
|
if (projectList === null) {
|
|
pendingFormValidation = true;
|
|
return;
|
|
}
|
|
projectNameStatus.empty();
|
|
if (!/^[a-zA-Z0-9\-_]+$/.test(projectName) || projectList[projectName]) {
|
|
projectNameInput.addClass("input-error");
|
|
$('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>').appendTo(projectNameStatus);
|
|
projectNameValid = false;
|
|
valid = false;
|
|
if (projectList[projectName]) {
|
|
projectNameSublabel.text("Project already exists");
|
|
} else {
|
|
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
|
|
}
|
|
} else {
|
|
projectNameInput.removeClass("input-error");
|
|
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
|
|
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
|
|
projectNameValid = true;
|
|
}
|
|
projectNameLastChecked = projectName;
|
|
}
|
|
valid = projectNameValid;
|
|
|
|
var projectType = $(".projects-dialog-screen-create-type.selected").data('type');
|
|
if (projectType === 'copy') {
|
|
if (!copyProject) {
|
|
valid = false;
|
|
}
|
|
} else if (projectType === 'clone') {
|
|
var repo = projectRepoInput.val();
|
|
|
|
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
|
|
var validRepo = repo.length > 0 && !/\s/.test(repo);
|
|
if (/^https?:\/\/[^/]+@/i.test(repo)) {
|
|
$("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url");
|
|
validRepo = false;
|
|
}
|
|
if (!validRepo) {
|
|
if (projectRepoChanged) {
|
|
projectRepoInput.addClass("input-error");
|
|
}
|
|
valid = false;
|
|
} else {
|
|
projectRepoInput.removeClass("input-error");
|
|
}
|
|
if (/^https?:\/\//.test(repo)) {
|
|
$(".projects-dialog-screen-create-row-creds").show();
|
|
$(".projects-dialog-screen-create-row-sshkey").hide();
|
|
} else if (/^(?:ssh|[\S]+?@[\S]+?):(?:\/\/)?/.test(repo)) {
|
|
$(".projects-dialog-screen-create-row-creds").hide();
|
|
$(".projects-dialog-screen-create-row-sshkey").show();
|
|
// if ( !getSelectedSSHKey(projectRepoSSHKeySelect) ) {
|
|
// valid = false;
|
|
// }
|
|
} else {
|
|
$(".projects-dialog-screen-create-row-creds").hide();
|
|
$(".projects-dialog-screen-create-row-sshkey").hide();
|
|
}
|
|
|
|
|
|
} else if (projectType === 'empty') {
|
|
var flowFile = projectFlowFileInput.val();
|
|
if (flowFile === "" || !/\.json$/.test(flowFile)) {
|
|
valid = false;
|
|
if (!projectFlowFileInput.hasClass("input-error")) {
|
|
projectFlowFileInput.addClass("input-error");
|
|
projectFlowFileInput.next().empty().append('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>');
|
|
}
|
|
} else {
|
|
if (projectFlowFileInput.hasClass("input-error")) {
|
|
projectFlowFileInput.removeClass("input-error");
|
|
projectFlowFileInput.next().empty();
|
|
}
|
|
}
|
|
|
|
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()!==''
|
|
}
|
|
}
|
|
} else if (projectType === 'open') {
|
|
valid = !!selectedProject;
|
|
}
|
|
|
|
$("#projects-dialog-create").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
|
|
}
|
|
|
|
row = $('<div class="form-row button-group"></div>').appendTo(container);
|
|
|
|
var openProject = $('<button data-type="open" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>Open Project</button>').appendTo(row);
|
|
var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Create Project</button>').appendTo(row);
|
|
// var createAsCopy = $('<button data-type="copy" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
|
|
var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>Clone Repository</button>').appendTo(row);
|
|
// var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row);
|
|
row.find(".projects-dialog-screen-create-type").click(function(evt) {
|
|
evt.preventDefault();
|
|
container.find(".projects-dialog-screen-create-type").removeClass('selected');
|
|
$(this).addClass('selected');
|
|
container.find(".projects-dialog-screen-create-row").hide();
|
|
container.find(".projects-dialog-screen-create-row-"+$(this).data('type')).show();
|
|
validateForm();
|
|
projectNameInput.focus();
|
|
switch ($(this).data('type')) {
|
|
case "open": $("#projects-dialog-create").text("Open project"); break;
|
|
case "empty": $("#projects-dialog-create").text("Create project"); break;
|
|
case "clone": $("#projects-dialog-create").text("Clone project"); break;
|
|
}
|
|
})
|
|
|
|
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-open"></div>').hide().appendTo(container);
|
|
createProjectList({
|
|
canSelectActive: false,
|
|
dblclick: function(project) {
|
|
selectedProject = project;
|
|
$("#projects-dialog-create").click();
|
|
},
|
|
select: function(project) {
|
|
selectedProject = project;
|
|
validateForm();
|
|
},
|
|
delete: function(project) {
|
|
if (projectList) {
|
|
delete projectList[project.name];
|
|
}
|
|
selectedProject = null;
|
|
|
|
validateForm();
|
|
}
|
|
}).appendTo(row);
|
|
|
|
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 for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
|
|
|
|
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
|
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
|
|
var projectNameStatus = $('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
|
|
|
var projectNameInputChanged = false;
|
|
var projectNameLastChecked = "";
|
|
var projectNameValid;
|
|
var checkProjectName;
|
|
var autoInsertedName = "";
|
|
|
|
|
|
projectNameInput.on("change keyup paste",function() {
|
|
projectNameInputChanged = (projectNameInput.val() !== projectNameLastChecked);
|
|
if (checkProjectName) {
|
|
clearTimeout(checkProjectName);
|
|
} else if (projectNameInputChanged) {
|
|
projectNameStatus.empty();
|
|
$('<img src="red/images/spin.svg"/>').appendTo(projectNameStatus);
|
|
if (projectNameInput.val() === '') {
|
|
validateForm();
|
|
return;
|
|
}
|
|
}
|
|
checkProjectName = setTimeout(function() {
|
|
validateForm();
|
|
checkProjectName = null;
|
|
},300)
|
|
});
|
|
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
|
|
|
|
// Empty Project
|
|
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
|
|
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row);
|
|
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" 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 for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row);
|
|
subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
|
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val("flow.json")
|
|
.on("change keyup paste",validateForm)
|
|
.appendTo(subrow);
|
|
$('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
|
$('<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();
|
|
if ($("input[name=projects-encryption-key]:checked").val() === 'custom') {
|
|
emptyProjectCredentialInput.focus();
|
|
}
|
|
} 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">Encryption key</label>').appendTo(row);
|
|
// row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
|
|
emptyProjectCredentialInput = $('<input type="password"></input>').appendTo(row);
|
|
emptyProjectCredentialInput.on("change keyup paste", validateForm);
|
|
$('<label class="projects-edit-form-sublabel"><small>A phrase to secure your credentials with</small></label>').appendTo(row);
|
|
|
|
|
|
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
|
|
$('<div class="" style="padding: 5px 20px;"><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');
|
|
if (val === "custom") {
|
|
emptyProjectCredentialInput.focus();
|
|
}
|
|
validateForm();
|
|
})
|
|
|
|
// Clone Project
|
|
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
|
|
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row);
|
|
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
|
|
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row);
|
|
|
|
var projectRepoChanged = false;
|
|
var lastProjectRepo = "";
|
|
projectRepoInput.on("change keyup paste",function() {
|
|
projectRepoChanged = true;
|
|
var repo = $(this).val();
|
|
if (lastProjectRepo !== repo) {
|
|
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
|
|
}
|
|
lastProjectRepo = repo;
|
|
|
|
var m = /\/([^/]+?)(?:\.git)?$/.exec(repo);
|
|
if (m) {
|
|
var projectName = projectNameInput.val();
|
|
if (projectName === "" || projectName === autoInsertedName) {
|
|
autoInsertedName = m[1];
|
|
projectNameInput.val(autoInsertedName);
|
|
projectNameInput.change();
|
|
}
|
|
}
|
|
validateForm();
|
|
});
|
|
|
|
|
|
var cloneAuthRows = $('<div class="hide projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').hide().appendTo(container);
|
|
row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows);
|
|
$('<div><i class="fa fa-warning"></i> Authentication failed</div>').appendTo(row);
|
|
|
|
// Repo credentials - username/password ----------------
|
|
row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows);
|
|
|
|
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
|
|
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow);
|
|
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
|
|
|
|
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
|
|
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow);
|
|
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
|
|
// -----------------------------------------------------
|
|
|
|
// Repo credentials - key/passphrase -------------------
|
|
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows);
|
|
subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
|
|
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Key</label>').appendTo(subrow);
|
|
projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow);
|
|
|
|
$.getJSON("settings/user/keys", function(data) {
|
|
var count = 0;
|
|
data.keys.forEach(function(key) {
|
|
projectRepoSSHKeySelect.append($("<option></option>").val(key.name).text(key.name));
|
|
count++;
|
|
});
|
|
if (count === 0) {
|
|
projectRepoSSHKeySelect.addClass("input-error");
|
|
projectRepoSSHKeySelect.attr("disabled",true);
|
|
sshwarningRow.show();
|
|
} else {
|
|
projectRepoSSHKeySelect.removeClass("input-error");
|
|
projectRepoSSHKeySelect.attr("disabled",false);
|
|
sshwarningRow.hide();
|
|
}
|
|
});
|
|
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
|
|
$('<label for="projects-dialog-screen-create-project-repo-passphrase">Passphrase</label>').appendTo(subrow);
|
|
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
|
|
|
|
subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
|
|
var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
|
|
$('<div class="form-row"><i class="fa fa-warning"></i> Before you can clone a repository over ssh you must add an SSH key to access it.</div>').appendTo(sshwarningRow);
|
|
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
|
|
$('<button class="editor-button">Add an ssh key</button>').appendTo(subrow).click(function(e) {
|
|
e.preventDefault();
|
|
$('#projects-dialog-cancel').click();
|
|
RED.userSettings.show('gitconfig');
|
|
setTimeout(function() {
|
|
$("#user-settings-gitconfig-add-key").click();
|
|
},500);
|
|
});
|
|
// -----------------------------------------------------
|
|
|
|
|
|
// 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="password"></input>').appendTo(row);
|
|
|
|
|
|
switch(options.screen||"empty") {
|
|
case "empty": createAsEmpty.click(); break;
|
|
case "open": openProject.click(); break;
|
|
case "clone": createAsClone.click(); break;
|
|
}
|
|
|
|
setTimeout(function() {
|
|
if ((options.screen||"empty") !== "open") {
|
|
projectNameInput.focus();
|
|
} else {
|
|
$("#projects-dialog-project-list-search").focus();
|
|
}
|
|
},50);
|
|
return container;
|
|
},
|
|
buttons: function(options) {
|
|
var initialLabel;
|
|
switch (options.screen||"empty") {
|
|
case "open": initialLabel = "Open project"; break;
|
|
case "empty": initialLabel = "Create project"; break;
|
|
case "clone": initialLabel = "Clone project"; break;
|
|
}
|
|
return [
|
|
{
|
|
id: "projects-dialog-cancel",
|
|
text: RED._("common.label.cancel"),
|
|
click: function() {
|
|
$( this ).dialog( "close" );
|
|
}
|
|
},
|
|
{
|
|
id: "projects-dialog-create",
|
|
text: initialLabel,
|
|
class: "primary disabled",
|
|
disabled: true,
|
|
click: function() {
|
|
var projectType = $(".projects-dialog-screen-create-type.selected").data('type');
|
|
var projectData = {
|
|
name: projectNameInput.val(),
|
|
}
|
|
if (projectType === 'empty') {
|
|
projectData.summary = projectSummaryInput.val();
|
|
projectData.files = {
|
|
flow: projectFlowFileInput.val()
|
|
};
|
|
var encryptionState = $("input[name=projects-encryption-type]:checked").val();
|
|
if (encryptionState === 'enabled') {
|
|
projectData.credentialSecret = emptyProjectCredentialInput.val();
|
|
} else {
|
|
// Disabled encryption by explicitly setting credSec to false
|
|
projectData.credentialSecret = false;
|
|
}
|
|
|
|
|
|
} else if (projectType === 'copy') {
|
|
projectData.copy = copyProject.name;
|
|
} else if (projectType === 'clone') {
|
|
projectData.credentialSecret = projectSecretInput.val();
|
|
var repoUrl = projectRepoInput.val();
|
|
var metaData = {};
|
|
if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repoUrl)) {
|
|
var selected = projectRepoSSHKeySelect.val();//false;//getSelectedSSHKey(projectRepoSSHKeySelect);
|
|
if ( selected ) {
|
|
projectData.git = {
|
|
remotes: {
|
|
'origin': {
|
|
url: repoUrl,
|
|
keyFile: selected,
|
|
passphrase: projectRepoPassphrase.val()
|
|
}
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
console.log("Error! Can't get selected SSH key path.");
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
projectData.git = {
|
|
remotes: {
|
|
'origin': {
|
|
url: repoUrl,
|
|
username: projectRepoUserInput.val(),
|
|
password: projectRepoPasswordInput.val()
|
|
}
|
|
}
|
|
};
|
|
}
|
|
} else if (projectType === 'open') {
|
|
return switchProject(selectedProject.name,function(err,data) {
|
|
dialog.dialog( "close" );
|
|
if (err) {
|
|
if (err.error !== 'credentials_load_failed') {
|
|
console.log("unexpected_error",err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
$(".projects-dialog-screen-create-row-auth-error").hide();
|
|
|
|
projectRepoUserInput.removeClass("input-error");
|
|
projectRepoPasswordInput.removeClass("input-error");
|
|
projectRepoSSHKeySelect.removeClass("input-error");
|
|
projectRepoPassphrase.removeClass("input-error");
|
|
|
|
RED.deploy.setDeployInflight(true);
|
|
RED.projects.settings.switchProject(projectData.name);
|
|
|
|
sendRequest({
|
|
url: "projects",
|
|
type: "POST",
|
|
handleAuthFail: false,
|
|
responses: {
|
|
200: function(data) {
|
|
dialog.dialog( "close" );
|
|
},
|
|
400: {
|
|
'project_exists': function(error) {
|
|
console.log("already exists");
|
|
},
|
|
'git_error': function(error) {
|
|
console.log("git error",error);
|
|
},
|
|
'git_connection_failed': function(error) {
|
|
projectRepoInput.addClass("input-error");
|
|
$("#projects-dialog-screen-create-project-repo-label small").text("Connection failed");
|
|
},
|
|
'git_not_a_repository': function(error) {
|
|
projectRepoInput.addClass("input-error");
|
|
$("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository");
|
|
},
|
|
'git_repository_not_found': function(error) {
|
|
projectRepoInput.addClass("input-error");
|
|
$("#projects-dialog-screen-create-project-repo-label small").text("Repository not found");
|
|
},
|
|
'git_auth_failed': function(error) {
|
|
$(".projects-dialog-screen-create-row-auth-error").show();
|
|
|
|
projectRepoUserInput.addClass("input-error");
|
|
projectRepoPasswordInput.addClass("input-error");
|
|
// getRepoAuthDetails(req);
|
|
projectRepoSSHKeySelect.addClass("input-error");
|
|
projectRepoPassphrase.addClass("input-error");
|
|
},
|
|
'missing_flow_file': function(error) {
|
|
// This is handled via a runtime notification.
|
|
dialog.dialog("close");
|
|
},
|
|
'project_empty': function(error) {
|
|
// This is handled via a runtime notification.
|
|
dialog.dialog("close");
|
|
},
|
|
'credentials_load_failed': function(error) {
|
|
// This is handled via a runtime notification.
|
|
dialog.dialog("close");
|
|
},
|
|
'*': function(error) {
|
|
reportUnexpectedError(error);
|
|
$( dialog ).dialog( "close" );
|
|
}
|
|
}
|
|
}
|
|
},projectData).then(function() {
|
|
RED.events.emit("project:change", {name:name});
|
|
}).always(function() {
|
|
setTimeout(function() {
|
|
RED.deploy.setDeployInflight(false);
|
|
},500);
|
|
})
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
})()
|
|
}
|
|
}
|
|
|
|
function switchProject(name,done) {
|
|
RED.deploy.setDeployInflight(true);
|
|
RED.projects.settings.switchProject(name);
|
|
sendRequest({
|
|
url: "projects/"+name,
|
|
type: "PUT",
|
|
requireCleanWorkspace: true,
|
|
responses: {
|
|
200: function(data) {
|
|
done(null,data);
|
|
},
|
|
400: {
|
|
'*': done
|
|
},
|
|
}
|
|
},{active:true}).then(function() {
|
|
RED.events.emit("project:change", {name:name});
|
|
}).always(function() {
|
|
setTimeout(function() {
|
|
RED.deploy.setDeployInflight(false);
|
|
},500);
|
|
})
|
|
}
|
|
|
|
function deleteProject(row,name,done) {
|
|
var cover = $('<div>').css({
|
|
background:"white",
|
|
position:"absolute",
|
|
top:0,right:0,bottom:0,left:"100%",
|
|
overflow:"hidden",
|
|
padding: "5px 20px",
|
|
transition: "left 0.4s",
|
|
whitespace: "nowrap",
|
|
width:"1000px"
|
|
}).click(function(evt) { evt.stopPropagation(); }).appendTo(row);
|
|
$('<span>').css({"lineHeight":"40px"}).text("Are you sure you want to delete this project?").appendTo(cover);
|
|
$('<button style="margin-left:20px" class="editor-button">Cancel</button>')
|
|
.appendTo(cover)
|
|
.click(function(e) {
|
|
e.stopPropagation();
|
|
cover.remove();
|
|
done(true);
|
|
});
|
|
$('<button style="margin-left:20px" class="editor-button primary">Delete</button>')
|
|
.appendTo(cover)
|
|
.click(function(e) {
|
|
e.stopPropagation();
|
|
cover.remove();
|
|
sendRequest({
|
|
url: "projects/"+name,
|
|
type: "DELETE",
|
|
responses: {
|
|
200: function(data) {
|
|
done(false);
|
|
},
|
|
400: {
|
|
'unexpected_error': function(error) {
|
|
cover.remove();
|
|
done(true);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
setTimeout(function() {
|
|
cover.css("left",0);
|
|
},50);
|
|
//
|
|
}
|
|
|
|
function show(s,options) {
|
|
if (!dialog) {
|
|
RED.projects.init();
|
|
}
|
|
var screen = screens[s];
|
|
var container = screen.content(options||{});
|
|
|
|
dialogBody.empty();
|
|
var buttons = screen.buttons;
|
|
if (typeof buttons === 'function') {
|
|
buttons = buttons(options||{});
|
|
}
|
|
dialog.dialog('option','buttons',buttons);
|
|
dialogBody.append(container);
|
|
dialog.dialog('option','title',screen.title||"");
|
|
dialog.dialog("open");
|
|
dialog.dialog({position: { 'my': 'center top', 'at': 'center top+10%', 'of': window }});
|
|
}
|
|
|
|
function createProjectList(options) {
|
|
options = options||{};
|
|
var height = options.height || "300px";
|
|
var container = $('<div></div>',{class:"projects-dialog-project-list-container" });
|
|
var filterTerm = "";
|
|
|
|
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(container);
|
|
var searchInput = $('<input id="projects-dialog-project-list-search" type="text" placeholder="search your projects">').appendTo(searchDiv).searchBox({
|
|
//data-i18n="[placeholder]menu.label.searchInput"
|
|
delay: 200,
|
|
change: function() {
|
|
filterTerm = $(this).val().toLowerCase();
|
|
list.editableList('filter');
|
|
if (selectedListItem && !selectedListItem.is(":visible")) {
|
|
selectedListItem.children().children().removeClass('selected');
|
|
selectedListItem = list.children(":visible").first();
|
|
selectedListItem.children().children().addClass('selected');
|
|
if (options.select) {
|
|
options.select(selectedListItem.children().data('data'));
|
|
}
|
|
} else {
|
|
selectedListItem = list.children(":visible").first();
|
|
selectedListItem.children().children().addClass('selected');
|
|
if (options.select) {
|
|
options.select(selectedListItem.children().data('data'));
|
|
}
|
|
}
|
|
ensureSelectedIsVisible();
|
|
}
|
|
});
|
|
var selectedListItem;
|
|
|
|
searchInput.on('keydown',function(evt) {
|
|
if (evt.keyCode === 40) {
|
|
evt.preventDefault();
|
|
// Down
|
|
var next = selectedListItem;
|
|
if (selectedListItem) {
|
|
do {
|
|
next = next.next();
|
|
} while(next.length !== 0 && !next.is(":visible"));
|
|
if (next.length === 0) {
|
|
return;
|
|
}
|
|
selectedListItem.children().children().removeClass('selected');
|
|
} else {
|
|
next = list.children(":visible").first();
|
|
}
|
|
selectedListItem = next;
|
|
selectedListItem.children().children().addClass('selected');
|
|
if (options.select) {
|
|
options.select(selectedListItem.children().data('data'));
|
|
}
|
|
ensureSelectedIsVisible();
|
|
} else if (evt.keyCode === 38) {
|
|
evt.preventDefault();
|
|
// Up
|
|
var prev = selectedListItem;
|
|
if (selectedListItem) {
|
|
do {
|
|
prev = prev.prev();
|
|
} while(prev.length !== 0 && !prev.is(":visible"));
|
|
if (prev.length === 0) {
|
|
return;
|
|
}
|
|
selectedListItem.children().children().removeClass('selected');
|
|
} else {
|
|
prev = list.children(":visible").first();
|
|
}
|
|
selectedListItem = prev;
|
|
selectedListItem.children().children().addClass('selected');
|
|
if (options.select) {
|
|
options.select(selectedListItem.children().data('data'));
|
|
}
|
|
ensureSelectedIsVisible();
|
|
} else if (evt.keyCode === 13) {
|
|
evt.preventDefault();
|
|
// Enter
|
|
if (selectedListItem) {
|
|
if (options.dblclick) {
|
|
options.dblclick(selectedListItem.children().data('data'));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
searchInput.i18n();
|
|
|
|
var ensureSelectedIsVisible = function() {
|
|
var selectedEntry = list.find(".projects-dialog-project-list-entry.selected").parent().parent();
|
|
if (selectedEntry.length === 1) {
|
|
var scrollWindow = listContainer;
|
|
var scrollHeight = scrollWindow.height();
|
|
var scrollOffset = scrollWindow.scrollTop();
|
|
var y = selectedEntry.position().top;
|
|
var h = selectedEntry.height();
|
|
if (y+h > scrollHeight) {
|
|
scrollWindow.animate({scrollTop: '-='+(scrollHeight-y-h)},50);
|
|
} else if (y<0) {
|
|
scrollWindow.animate({scrollTop: '+='+y},50);
|
|
}
|
|
}
|
|
}
|
|
|
|
var listContainer = $('<div></div>',{class:"projects-dialog-project-list-inner-container" }).appendTo(container);
|
|
|
|
var list = $('<ol>',{class:"projects-dialog-project-list"}).appendTo(listContainer).editableList({
|
|
addButton: false,
|
|
height:"auto",
|
|
scrollOnAdd: false,
|
|
addItem: function(row,index,entry) {
|
|
var header = $('<div></div>',{class:"projects-dialog-project-list-entry"}).appendTo(row);
|
|
$('<span class="projects-dialog-project-list-entry-icon"><i class="fa fa-archive"></i></span>').appendTo(header);
|
|
$('<span class="projects-dialog-project-list-entry-name" style=""></span>').text(entry.name).appendTo(header);
|
|
if (activeProject && activeProject.name === entry.name) {
|
|
header.addClass("projects-list-entry-current");
|
|
$('<span class="projects-dialog-project-list-entry-current">current</span>').appendTo(header);
|
|
if (options.canSelectActive === false) {
|
|
// active project cannot be selected; so skip the rest
|
|
return
|
|
}
|
|
}
|
|
|
|
header.addClass("selectable");
|
|
|
|
var tools = $('<div class="projects-dialog-project-list-entry-tools"></div>').appendTo(header);
|
|
$('<button class="editor-button editor-button-small" style="float: right;"><i class="fa fa-trash"></i></button>')
|
|
.appendTo(tools)
|
|
.click(function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
deleteProject(row,entry.name, function(cancelled) {
|
|
if (!cancelled) {
|
|
row.fadeOut(300,function() {
|
|
list.editableList('removeItem',entry);
|
|
if (options.delete) {
|
|
options.delete(entry);
|
|
}
|
|
});
|
|
}
|
|
})
|
|
});
|
|
|
|
|
|
row.click(function(evt) {
|
|
$('.projects-dialog-project-list-entry').removeClass('selected');
|
|
header.addClass('selected');
|
|
selectedListItem = row.parent();
|
|
if (options.select) {
|
|
options.select(entry);
|
|
}
|
|
ensureSelectedIsVisible();
|
|
searchInput.focus();
|
|
})
|
|
if (options.dblclick) {
|
|
row.dblclick(function(evt) {
|
|
evt.preventDefault();
|
|
options.dblclick(entry);
|
|
})
|
|
}
|
|
},
|
|
filter: function(data) {
|
|
if (filterTerm === "") { return true; }
|
|
return data.name.toLowerCase().indexOf(filterTerm) !== -1;
|
|
}
|
|
});
|
|
$.getJSON("projects", function(data) {
|
|
data.projects.forEach(function(project) {
|
|
list.editableList('addItem',{name:project});
|
|
});
|
|
})
|
|
return container;
|
|
}
|
|
|
|
|
|
|
|
function requireCleanWorkspace(done) {
|
|
if (RED.nodes.dirty()) {
|
|
var message = '<p>You have undeployed changes that will be lost.</p><p>Do you want to continue?</p>';
|
|
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();
|
|
done(true);
|
|
}
|
|
},{
|
|
text: 'Continue',
|
|
click: function() {
|
|
cleanNotification.close();
|
|
done(false);
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
}
|
|
}
|
|
|
|
function sendRequest(options,body) {
|
|
// dialogBody.hide();
|
|
// console.log(options.url,body);
|
|
|
|
if (options.requireCleanWorkspace && RED.nodes.dirty()) {
|
|
var thenCallback;
|
|
var alwaysCallback;
|
|
requireCleanWorkspace(function(cancelled) {
|
|
if (cancelled) {
|
|
if (options.cancel) {
|
|
options.cancel();
|
|
if (alwaysCallback) {
|
|
alwaysCallback();
|
|
}
|
|
}
|
|
} else {
|
|
delete options.requireCleanWorkspace;
|
|
sendRequest(options,body).then(function() {
|
|
if (thenCallback) {
|
|
thenCallback();
|
|
}
|
|
}).always(function() {
|
|
if (alwaysCallback) {
|
|
alwaysCallback();
|
|
}
|
|
|
|
})
|
|
}
|
|
})
|
|
// What follows is a very hacky Promise-like api thats good enough
|
|
// for our needs.
|
|
return {
|
|
then: function(done) {
|
|
thenCallback = done;
|
|
return { always: function(done) { alwaysCallback = done; }}
|
|
},
|
|
always: function(done) { alwaysCallback = done; }
|
|
}
|
|
}
|
|
|
|
var start = Date.now();
|
|
// TODO: this is specific to the dialog-based requests
|
|
$(".projects-dialog-spinner").show();
|
|
$("#projects-dialog").parent().find(".ui-dialog-buttonset").children().css("visibility","hidden")
|
|
if (body) {
|
|
options.data = JSON.stringify(body);
|
|
options.contentType = "application/json; charset=utf-8";
|
|
}
|
|
var resultCallback;
|
|
var resultCallbackArgs;
|
|
return $.ajax(options).done(function(data,textStatus,xhr) {
|
|
if (options.responses && options.responses[200]) {
|
|
resultCallback = options.responses[200];
|
|
resultCallbackArgs = data;
|
|
}
|
|
}).fail(function(xhr,textStatus,err) {
|
|
if (options.responses && options.responses[xhr.status]) {
|
|
var responses = options.responses[xhr.status];
|
|
if (typeof responses === 'function') {
|
|
resultCallback = responses;
|
|
resultCallbackArgs = {error:responses.statusText};
|
|
return;
|
|
} else if (options.handleAuthFail !== false && xhr.responseJSON.error === 'git_auth_failed') {
|
|
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
|
|
|
|
var message = $('<div>'+
|
|
'<div class="form-row">Authentication required for repository:</div>'+
|
|
'<div class="form-row"><div style="margin-left: 20px;">'+url+'</div></div>'+
|
|
'</div>');
|
|
|
|
var isSSH = false;
|
|
if (/^https?:\/\//.test(url)) {
|
|
$('<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>').appendTo(message);
|
|
} else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) {
|
|
isSSH = true;
|
|
var row = $('<div class="form-row"></div>').appendTo(message);
|
|
$('<label for="projects-user-auth-key">SSH Key</label>').appendTo(row);
|
|
var projectRepoSSHKeySelect = $('<select id="projects-user-auth-key">').width('70%').appendTo(row);
|
|
$.getJSON("settings/user/keys", function(data) {
|
|
var count = 0;
|
|
data.keys.forEach(function(key) {
|
|
projectRepoSSHKeySelect.append($("<option></option>").val(key.name).text(key.name));
|
|
count++;
|
|
});
|
|
if (count === 0) {
|
|
//TODO: handle no keys yet setup
|
|
}
|
|
});
|
|
row = $('<div class="form-row"></div>').appendTo(message);
|
|
$('<label for="projects-user-auth-passphrase">Passphrase</label>').appendTo(row);
|
|
$('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row);
|
|
}
|
|
|
|
var notification = RED.notify(message,{
|
|
type:"error",
|
|
fixed: 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() {
|
|
body = body || {};
|
|
var authBody = {};
|
|
if (isSSH) {
|
|
authBody.keyFile = $('#projects-user-auth-key').val();
|
|
authBody.passphrase = $('#projects-user-auth-passphrase').val();
|
|
} else {
|
|
authBody.username = $('#projects-user-auth-username').val();
|
|
authBody.password = $('#projects-user-auth-password').val();
|
|
}
|
|
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+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'),
|
|
type: "PUT",
|
|
responses: {
|
|
0: function(error) {
|
|
done(error,null);
|
|
},
|
|
200: function(data) {
|
|
done(null,data);
|
|
},
|
|
400: {
|
|
'unexpected_error': function(error) {
|
|
done(error,null);
|
|
}
|
|
},
|
|
}
|
|
},{auth:authBody});
|
|
}
|
|
}
|
|
]
|
|
});
|
|
return;
|
|
} else if (responses[xhr.responseJSON.error]) {
|
|
resultCallback = responses[xhr.responseJSON.error];
|
|
resultCallbackArgs = xhr.responseJSON;
|
|
return;
|
|
} else if (responses['*']) {
|
|
resultCallback = responses['*'];
|
|
resultCallbackArgs = xhr.responseJSON;
|
|
return;
|
|
}
|
|
}
|
|
console.log("Unhandled error response:");
|
|
console.log(xhr);
|
|
console.log(textStatus);
|
|
console.log(err);
|
|
}).always(function() {
|
|
var delta = Date.now() - start;
|
|
delta = Math.max(0,500-delta);
|
|
setTimeout(function() {
|
|
// dialogBody.show();
|
|
$(".projects-dialog-spinner").hide();
|
|
$("#projects-dialog").parent().find(".ui-dialog-buttonset").children().css("visibility","")
|
|
if (resultCallback) {
|
|
resultCallback(resultCallbackArgs)
|
|
}
|
|
},delta);
|
|
});
|
|
}
|
|
|
|
function createBranchList(options) {
|
|
var branchFilterTerm = "";
|
|
var branchFilterCreateItem;
|
|
var branches = [];
|
|
var branchPrefix = "";
|
|
var container = $('<div class="projects-branch-list">').appendTo(options.container);
|
|
|
|
var branchFilter = $('<input type="text">').attr('placeholder',options.placeholder).appendTo(container).searchBox({
|
|
delay: 200,
|
|
change: function() {
|
|
branchFilterTerm = $(this).val();
|
|
if (/(\.\.|\/\.|[?*[~^: \\]|\/\/|\/.$|\/$)/.test(branchFilterTerm)) {
|
|
if (!branchFilterCreateItem.hasClass("input-error")) {
|
|
branchFilterCreateItem.addClass("input-error");
|
|
branchFilterCreateItem.find("i").addClass("fa-warning").removeClass("fa-code-fork");
|
|
}
|
|
branchFilterCreateItem.find("span").text("Invalid branch: "+branchPrefix+branchFilterTerm);
|
|
} else {
|
|
if (branchFilterCreateItem.hasClass("input-error")) {
|
|
branchFilterCreateItem.removeClass("input-error");
|
|
branchFilterCreateItem.find("i").removeClass("fa-warning").addClass("fa-code-fork");
|
|
}
|
|
branchFilterCreateItem.find(".sidebar-version-control-branch-list-entry-create-name").text(branchPrefix+branchFilterTerm);
|
|
}
|
|
branchList.editableList("filter");
|
|
}
|
|
});
|
|
var branchList = $("<ol>",{style:"height: 130px;"}).appendTo(container);
|
|
branchList.editableList({
|
|
addButton: false,
|
|
scrollOnAdd: false,
|
|
addItem: function(row,index,entry) {
|
|
var container = $('<div class="sidebar-version-control-branch-list-entry">').appendTo(row);
|
|
if (!entry.hasOwnProperty('commit')) {
|
|
branchFilterCreateItem = container;
|
|
$('<i class="fa fa-code-fork"></i>').appendTo(container);
|
|
$('<span>').text("Create branch:").appendTo(container);
|
|
$('<div class="sidebar-version-control-branch-list-entry-create-name" style="margin-left: 10px;">').text(entry.name).appendTo(container);
|
|
} else {
|
|
$('<i class="fa fa-code-fork"></i>').appendTo(container);
|
|
$('<span>').text(entry.name).appendTo(container);
|
|
if (entry.current) {
|
|
container.addClass("selected");
|
|
$('<span class="current"></span>').text(options.currentLabel||"current").appendTo(container);
|
|
}
|
|
}
|
|
container.click(function(evt) {
|
|
evt.preventDefault();
|
|
if ($(this).hasClass('input-error')) {
|
|
return;
|
|
}
|
|
var body = {};
|
|
if (!entry.hasOwnProperty('commit')) {
|
|
body.name = branchFilter.val();
|
|
body.create = true;
|
|
if (options.remote) {
|
|
body.name = options.remote()+"/"+body.name;
|
|
}
|
|
} else {
|
|
if ($(this).hasClass('selected')) {
|
|
body.current = true;
|
|
}
|
|
body.name = entry.name;
|
|
}
|
|
if (options.onselect) {
|
|
options.onselect(body);
|
|
}
|
|
});
|
|
},
|
|
filter: function(data) {
|
|
var isCreateEntry = (!data.hasOwnProperty('commit'));
|
|
return (
|
|
isCreateEntry &&
|
|
(
|
|
branchFilterTerm !== "" &&
|
|
branches.indexOf(branchPrefix+branchFilterTerm) === -1
|
|
)
|
|
) ||
|
|
(
|
|
!isCreateEntry &&
|
|
data.name.indexOf(branchFilterTerm) !== -1
|
|
);
|
|
}
|
|
});
|
|
return {
|
|
refresh: function(url) {
|
|
branchFilter.searchBox("value","");
|
|
branchList.editableList('empty');
|
|
var start = Date.now();
|
|
var spinner = addSpinnerOverlay(container).addClass("projects-dialog-spinner-contain");
|
|
if (options.remote) {
|
|
branchPrefix = options.remote()+"/";
|
|
} else {
|
|
branchPrefix = "";
|
|
}
|
|
|
|
sendRequest({
|
|
url: url,
|
|
type: "GET",
|
|
responses: {
|
|
0: function(error) {
|
|
console.log(error);
|
|
},
|
|
200: function(result) {
|
|
branches = result.branches;
|
|
result.branches.forEach(function(b) {
|
|
branchList.editableList('addItem',b);
|
|
});
|
|
branchList.editableList('addItem',{});
|
|
setTimeout(function() {
|
|
spinner.remove();
|
|
},Math.max(300-(Date.now() - start),0));
|
|
},
|
|
400: {
|
|
'git_connection_failed': function(error) {
|
|
RED.notify(error.message,'error');
|
|
},
|
|
'git_not_a_repository': function(error) {
|
|
RED.notify(error.message,'error');
|
|
},
|
|
'git_repository_not_found': function(error) {
|
|
RED.notify(error.message,'error');
|
|
},
|
|
'unexpected_error': function(error) {
|
|
reportUnexpectedError(error);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
},
|
|
// addItem: function(data) { branchList.editableList('addItem',data) },
|
|
filter: function() { branchList.editableList('filter') },
|
|
focus: function() { branchFilter.focus() }
|
|
}
|
|
}
|
|
|
|
function addSpinnerOverlay(container) {
|
|
var spinner = $('<div class="projects-dialog-spinner"><img src="red/images/spin.svg"/></div>').appendTo(container);
|
|
return spinner;
|
|
}
|
|
|
|
function init() {
|
|
dialog = $('<div id="projects-dialog" class="hide node-red-dialog projects-edit-form"><form class="form-horizontal"></form><div class="projects-dialog-spinner hide"><img src="red/images/spin.svg"/></div></div>')
|
|
.appendTo("body")
|
|
.dialog({
|
|
modal: true,
|
|
autoOpen: false,
|
|
width: 600,
|
|
resizable: false,
|
|
open: function(e) {
|
|
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
|
// $("#header-shade").show();
|
|
// $("#editor-shade").show();
|
|
// $("#palette-shade").show();
|
|
// $("#sidebar-shade").show();
|
|
},
|
|
close: function(e) {
|
|
// $("#header-shade").hide();
|
|
// $("#editor-shade").hide();
|
|
// $("#palette-shade").hide();
|
|
// $("#sidebar-shade").hide();
|
|
}
|
|
});
|
|
dialogBody = dialog.find("form");
|
|
|
|
RED.actions.add("core:new-project",RED.projects.newProject);
|
|
RED.actions.add("core:open-project",RED.projects.selectProject);
|
|
RED.actions.add("core:show-project-settings",RED.projects.settings.show);
|
|
var projectsAPI = {
|
|
sendRequest:sendRequest,
|
|
createBranchList:createBranchList,
|
|
addSpinnerOverlay:addSpinnerOverlay,
|
|
reportUnexpectedError:reportUnexpectedError
|
|
};
|
|
RED.projects.settings.init(projectsAPI);
|
|
RED.projects.userSettings.init(projectsAPI);
|
|
RED.sidebar.versionControl.init(projectsAPI);
|
|
initScreens();
|
|
// initSidebar();
|
|
}
|
|
|
|
function createDefaultFileSet() {
|
|
if (!activeProject) {
|
|
throw new Error("Cannot create default file set without an active project");
|
|
} else if (!activeProject.empty) {
|
|
throw new Error("Cannot create default file set on a non-empty project");
|
|
}
|
|
if (!RED.user.hasPermission("projects.write")) {
|
|
RED.notify(RED._("user.errors.notAuthorized"),"error");
|
|
return;
|
|
}
|
|
createProjectOptions = {};
|
|
show('default-files',{existingProject: true});
|
|
}
|
|
|
|
function refresh(done) {
|
|
$.getJSON("projects",function(data) {
|
|
if (data.active) {
|
|
$.getJSON("projects/"+data.active, function(project) {
|
|
activeProject = project;
|
|
RED.sidebar.versionControl.refresh(true);
|
|
if (done) {
|
|
done(activeProject);
|
|
}
|
|
});
|
|
} else {
|
|
if (done) {
|
|
done(null);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function showNewProjectScreen() {
|
|
createProjectOptions = {};
|
|
if (!activeProject) {
|
|
show('welcome');
|
|
} else {
|
|
show('create',{screen:'empty'})
|
|
}
|
|
}
|
|
|
|
return {
|
|
init: init,
|
|
showStartup: function() {
|
|
if (!RED.user.hasPermission("projects.write")) {
|
|
RED.notify(RED._("user.errors.notAuthorized"),"error");
|
|
return;
|
|
}
|
|
show('welcome');
|
|
},
|
|
newProject: function() {
|
|
if (!RED.user.hasPermission("projects.write")) {
|
|
RED.notify(RED._("user.errors.notAuthorized"),"error");
|
|
return;
|
|
}
|
|
|
|
if (RED.nodes.dirty()) {
|
|
return requireCleanWorkspace(function(cancelled) {
|
|
if (!cancelled) {
|
|
showNewProjectScreen();
|
|
}
|
|
})
|
|
} else {
|
|
showNewProjectScreen();
|
|
}
|
|
},
|
|
selectProject: function() {
|
|
if (!RED.user.hasPermission("projects.write")) {
|
|
RED.notify(RED._("user.errors.notAuthorized"),"error");
|
|
return;
|
|
}
|
|
show('create',{screen:'open'})
|
|
},
|
|
showCredentialsPrompt: function() { //TODO: rename this function
|
|
if (!RED.user.hasPermission("projects.write")) {
|
|
RED.notify(RED._("user.errors.notAuthorized"),"error");
|
|
return;
|
|
}
|
|
RED.projects.settings.show('settings');
|
|
},
|
|
showFilesPrompt: function() { //TODO: rename this function
|
|
if (!RED.user.hasPermission("projects.write")) {
|
|
RED.notify(RED._("user.errors.notAuthorized"),"error");
|
|
return;
|
|
}
|
|
RED.projects.settings.show('settings');
|
|
},
|
|
showProjectDependencies: function() {
|
|
RED.projects.settings.show('deps');
|
|
},
|
|
createDefaultFileSet: createDefaultFileSet,
|
|
// showSidebar: showSidebar,
|
|
refresh: refresh,
|
|
editProject: function() {
|
|
RED.projects.settings.show();
|
|
},
|
|
getActiveProject: function() {
|
|
return activeProject;
|
|
}
|
|
}
|
|
})();
|