From e3520309fca48efc3fc096a95bbe729d5bd2a67f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 2 May 2018 13:38:50 +0100 Subject: [PATCH] Add clone project to welcome screen --- editor/js/ui/projects/projects.js | 392 +++++++++++++++++- editor/sass/projects.scss | 12 +- .../localfilesystem/projects/git/index.js | 6 +- 3 files changed, 393 insertions(+), 17 deletions(-) diff --git a/editor/js/ui/projects/projects.js b/editor/js/ui/projects/projects.js index 95c2344b7..8811a5f7f 100644 --- a/editor/js/ui/projects/projects.js +++ b/editor/js/ui/projects/projects.js @@ -80,6 +80,26 @@ RED.projects = (function() { $('

').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body); $('

').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); + var row = $('

').appendTo(body); + var createAsEmpty = $('').appendTo(row); + var createAsClone = $('').appendTo(row); + + createAsEmpty.click(function(e) { + e.preventDefault(); + createProjectOptions = { + action: "create" + } + show('git-config'); + }) + + createAsClone.click(function(e) { + e.preventDefault(); + createProjectOptions = { + action: "clone" + } + show('git-config'); + }) + return container; }, buttons: [ @@ -90,13 +110,6 @@ RED.projects = (function() { createProjectOptions = {}; $( this ).dialog( "close" ); } - }, - { - text: "Create your first project", // TODO: nls - class: "primary", - click: function() { - show('git-config'); - } } ] }, @@ -170,7 +183,11 @@ RED.projects = (function() { currentGitSettings.user.name = gitUsernameInput.val(); currentGitSettings.user.email = gitEmailInput.val(); RED.settings.set('git', currentGitSettings); - show('project-details'); + if (createProjectOptions.action === "create") { + show('project-details'); + } else if (createProjectOptions.action === "clone") { + show('clone-project'); + } } } ] @@ -303,6 +320,365 @@ RED.projects = (function() { } }; })(), + 'clone-project': (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 { + content: function(options) { + var container = $('
'); + migrateProjectHeader.appendTo(container); + var body = $('
').appendTo(container); + $('

').text("Clone a project").appendTo(body); + $('

').text("If you already have a git repository containing a project, you can clone it to get started.").appendTo(body); + + var projectList = null; + var pendingFormValidation = false; + $.getJSON("projects", function(data) { + projectList = {}; + data.projects.forEach(function(p) { + projectList[p] = true; + if (pendingFormValidation) { + pendingFormValidation = false; + validateForm(); + } + }) + }); + + + 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"); + $('').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"); + $('').appendTo(projectNameStatus); + projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); + projectNameValid = true; + } + projectNameLastChecked = projectName; + } + valid = projectNameValid; + + 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(); + } + + $("#projects-dialog-clone-project").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid); + } + + var row; + + row = $('

').appendTo(body); + $('').appendTo(row); + + var subrow = $('
').appendTo(row); + projectNameInput = $('').appendTo(subrow); + var projectNameStatus = $('
').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(); + $('').appendTo(projectNameStatus); + if (projectNameInput.val() === '') { + validateForm(); + return; + } + } + checkProjectName = setTimeout(function() { + validateForm(); + checkProjectName = null; + },300) + }); + projectNameSublabel = $('').appendTo(row).find("small"); + + row = $('
').appendTo(body); + $('').appendTo(row); + projectRepoInput = $('').appendTo(row); + $('').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 = $('
').appendTo(body); + row = $('
').hide().appendTo(cloneAuthRows); + $('
Authentication failed
').appendTo(row); + + // Repo credentials - username/password ---------------- + row = $('
').hide().appendTo(cloneAuthRows); + + var subrow = $('
').appendTo(row); + $('').appendTo(subrow); + projectRepoUserInput = $('').appendTo(subrow); + + subrow = $('
').appendTo(row); + $('').appendTo(subrow); + projectRepoPasswordInput = $('').appendTo(subrow); + // ----------------------------------------------------- + + // Repo credentials - key/passphrase ------------------- + row = $('
').hide().appendTo(cloneAuthRows); + subrow = $('
').appendTo(row); + $('').appendTo(subrow); + projectRepoSSHKeySelect = $("').appendTo(subrow); + + subrow = $('
').appendTo(cloneAuthRows); + var sshwarningRow = $('
').hide().appendTo(subrow); + $('
Before you can clone a repository over ssh you must add an SSH key to access it.
').appendTo(sshwarningRow); + subrow = $('
').appendTo(sshwarningRow); + $('').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 = $('
').appendTo(body); + $('').appendTo(row); + projectSecretInput = $('').appendTo(row); + + + + return container; + }, + buttons: function(options) { + return [ + { + text: "Back", + click: function() { + show('git-config'); + } + }, + { + id: "projects-dialog-clone-project", + disabled: true, + text: "Clone project", // TODO: nls + class: "primary disabled", + click: function() { + var projectType = $(".projects-dialog-screen-create-type.selected").data('type'); + var projectData = { + name: projectNameInput.val(), + } + 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() + } + } + }; + } + + $(".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); + }) + + } + } + ] + } + } + })(), 'default-files': (function() { var projectFlowFileInput; var projectCredentialFileInput; diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss index d9b2545ac..74fa801c9 100644 --- a/editor/sass/projects.scss +++ b/editor/sass/projects.scss @@ -139,16 +139,17 @@ } } button.editor-button { - width: calc(50% - 40px); + width: calc(50% - 80px); margin: 20px; - height: 175px; + height: auto; line-height: 2em; - font-size: 1.5em !important; + padding: 10px; + border-color: #aaa; i { - color: #ccc; + color: #aaa; } &:hover i { - color: #aaa; + color: #999; } } .button-group { @@ -160,7 +161,6 @@ button.projects-dialog-screen-create-type { height: auto; padding: 10px; - } .button-group { text-align: center; diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index 717a1a235..196aece83 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -48,7 +48,9 @@ function runGitCommand(args,cwd,env) { var err = new Error(stderr); err.stdout = stdout; err.stderr = stderr; - if (/fatal: could not read/i.test(stderr)) { + if(/Connection refused/i.test(stderr)) { + err.code = "git_connection_failed"; + } else if (/fatal: could not read/i.test(stderr)) { // Username/Password err.code = "git_auth_failed"; } else if(/HTTP Basic: Access denied/i.test(stderr)) { @@ -58,8 +60,6 @@ function runGitCommand(args,cwd,env) { } else if(/Host key verification failed/i.test(stderr)) { // TODO: handle host key verification errors separately err.code = "git_auth_failed"; - } else if(/Connection refused/i.test(stderr)) { - err.code = "git_connection_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) {