/** * 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; var screens = {}; function initScreens() { var migrateProjectHeader = $('
'); $('         ').appendTo(migrateProjectHeader) $('
').appendTo(migrateProjectHeader); var createProjectOptions = {}; screens = { 'welcome': { content: function(options) { var container = $('
'); migrateProjectHeader.appendTo(container); var body = $('
').appendTo(container); $('

').text("Hello! We have introduced 'projects' to Node-RED.").appendTo(body); $('

').text("This is a new way for you to manage your flow files and includes version control of your flows.").appendTo(body); $('

').text("To get started you can create your first project using your current flow files in a few easy steps.").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 option at any time.").appendTo(body); // var buttons = $('

').appendTo(container); // var createNew = $('').appendTo(buttons); // createNew.click(function(e) { // e.preventDefault(); // show('create'); // }); // var openExisting = $('').appendTo(buttons); // openExisting.click(function(e) { // e.preventDefault(); // show('open') // }); 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 container = $('
'); migrateProjectHeader.appendTo(container); var body = $('
').appendTo(container); $('

').text("Step 1 : Setup your version control client").appendTo(body); $('

').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); $('

').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); $('

').text("If your Git client is already configured, you can skip this step.").appendTo(body); var currentGitSettings = RED.settings.get('git') || {}; currentGitSettings.user = currentGitSettings.user || {}; var row = $('

').appendTo(body); $('').appendTo(row); gitUsernameInput = $('').val(currentGitSettings.user.name||"").appendTo(row); // $('
').text("This does not need to be your real name").appendTo(row); row = $('
').appendTo(body); $('').appendTo(row); gitEmailInput = $('').val(currentGitSettings.user.email||"").appendTo(row); // $('
').text("Something something email").appendTo(row); setTimeout(function() { gitUsernameInput.focus(); },50); return container; }, buttons: [ { // id: "clipboard-dialog-cancel", text: "Back", click: function() { show('welcome'); } }, { 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 container = $('
'); migrateProjectHeader.appendTo(container); var body = $('
').appendTo(container); $('

').text("Step 2 : Create your project").appendTo(body); $('

').text("A project contains your flow files, a README file, a package.json file and a settings file. It makes it much easier to share your flows with others and to collaborate on them.").appendTo(body); $('

').text("You can create multiple projects and quickly switch between them from the editor.").appendTo(body); $('

').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) { projectNameStatus.empty(); if (!/^[a-zA-Z0-9\-_]+$/.test(projectName)) { projectNameInput.addClass("input-error"); $('').appendTo(projectNameStatus); projectNameValid = false; valid = false; } else { projectNameInput.removeClass("input-error"); $('').appendTo(projectNameStatus); projectNameValid = true; } projectNameLastChecked = projectName; } valid = projectNameValid; $("#projects-dialog-create-name").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid); } var row = $('

').appendTo(body); $('').appendTo(row); var subrow = $('
').appendTo(row); projectNameInput = $('').val(createProjectOptions.name||"").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"); // Empty Project row = $('
').appendTo(body); $('').appendTo(row); projectSummaryInput = $('').val(createProjectOptions.summary||"").appendTo(row); $('').appendTo(row); setTimeout(function() { projectNameInput.focus(); },50); return container; }, buttons: [ { 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(); createProjectOptions.files = { migrateFiles: true }; show('encryption-config'); } } ] }; })(), 'encryption-config': (function() { var emptyProjectCredentialInput; return { content: function(options) { var container = $('
'); migrateProjectHeader.appendTo(container); var body = $('
').appendTo(container); $('

').text("Step 3 : Setup encryption of your credentials file").appendTo(body); // $('

').text("Your flow credentials file can be encrypted to keep its contents secure.").appendTo(body); if (RED.settings.flowEncryptionType === 'disabled') { $('

').text("Your flow credentials file is not currently encrypted.").appendTo(body); $('

').text("That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.").appendTo(body); $('

').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') { $('

').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') { $('

').text("Your flow credentials file is currently encrypted using a system-generated secret as the key. You should provide a new secret key for this project.").appendTo(body); } $('

').text("The secret will be copied into the settings for your new project. You can then manage the secret within the editor.").appendTo(body); } // var row = $('

').appendTo(body); // $('').appendTo(row); // var gitUsernameInput = $('').val(currentGitSettings.user.name||"").appendTo(row); // // $('
').text("This does not need to be your real name").appendTo(row); // // row = $('
').appendTo(body); // $('').appendTo(row); // var gitEmailInput = $('').val(currentGitSettings.user.email||"").appendTo(row); // // $('
').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 = $('
').appendTo(body); $('').appendTo(row); var credentialsBox = $('
').appendTo(row); var credentialsRightBox = $('
').appendTo(credentialsBox); var credentialsLeftBox = $('
').appendTo(credentialsBox); var credentialsEnabledBox = $('
').appendTo(credentialsLeftBox); $('').appendTo(credentialsEnabledBox); var credentialsDisabledBox = $('
').appendTo(credentialsLeftBox); $('').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 = $('
').appendTo(credentialsRightBox); $('').appendTo(row); row = $('
').appendTo(credentialsRightBox); $('').appendTo(row); row = $('
').appendTo(credentialsRightBox); emptyProjectCredentialInput = $('').appendTo(row); emptyProjectCredentialInput.on("change keyup paste", validateForm); row = $('
').hide().appendTo(credentialsRightBox); $('
The credentials file will not be encrypted and its contents easily read
').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: [ { // id: "clipboard-dialog-cancel", text: "Back", click: function() { show('project-details'); } }, { id: "projects-dialog-create-encryption", text: "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); sendRequest({ url: "projects", type: "POST", requireCleanWorkspace: true, handleAuthFail: false, responses: { 200: function(data) { createProjectOptions = {}; show('create-success'); }, 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); }, 'unexpected_error': function(error) { console.log("unexpected_error",error) } } } },createProjectOptions).always(function() { RED.deploy.setDeployInflight(false); }) } } ] } })(), 'create-success': { content: function(options) { var container = $('
'); migrateProjectHeader.appendTo(container); var body = $('
').appendTo(container); $('

').text("You have successfully created your first project!").appendTo(body); $('

').text("You can now continue to use Node-RED just as you always have.").appendTo(body); $('

').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); $('

').text("The new '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 emptyProjectCredentialInput; var projectRepoUserInput; var projectRepoPasswordInput; var projectNameSublabel; var projectRepoSSHKeySelect; var projectRepoPassphrase; var projectRepoRemoteName var projectRepoBranch; return { title: "Create a new project", // TODO: NLS content: function() { 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 container = $('

'); 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"); $('').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 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 = /^(?:git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+\.git(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo); if (!validRepo) { if (projectRepoChanged) { projectRepoInput.addClass("input-error"); } valid = false; } else { projectRepoInput.removeClass("input-error"); } if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repo)) { $(".projects-dialog-screen-create-row-creds").hide(); $(".projects-dialog-screen-create-row-passphrase").show(); $(".projects-dialog-screen-create-row-sshkey").show(); if ( !getSelectedSSHKey(projectRepoSSHKeySelect) ) { valid = false; } } else if (/^https?:\/\//.test(repo)) { $(".projects-dialog-screen-create-row-creds").show(); $(".projects-dialog-screen-create-row-passphrase").hide(); $(".projects-dialog-screen-create-row-sshkey").hide(); } else { $(".projects-dialog-screen-create-row-creds").show(); $(".projects-dialog-screen-create-row-passphrase").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(''); } } 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()!=='' } } } $("#projects-dialog-create").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid); } row = $('
').appendTo(container); var createAsEmpty = $('').appendTo(row); // var createAsCopy = $('').appendTo(row); var createAsClone = $('').appendTo(row); row.find(".projects-dialog-screen-create-type").click(function(evt) { evt.preventDefault(); $(".projects-dialog-screen-create-type").removeClass('selected'); $(this).addClass('selected'); $(".projects-dialog-screen-create-row").hide(); $(".projects-dialog-screen-create-row-"+$(this).data('type')).show(); validateForm(); projectNameInput.focus(); }) row = $('
').appendTo(container); $('').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"); // Empty Project row = $('
').appendTo(container); $('').appendTo(row); projectSummaryInput = $('').appendTo(row); $('').appendTo(row); row = $('
').appendTo(container); $('').appendTo(row); subrow = $('
').appendTo(row); projectFlowFileInput = $('').val("flow.json") .on("change keyup paste",validateForm) .appendTo(subrow); $('
').appendTo(subrow); $('').appendTo(row); row = $('
').appendTo(container); $('').appendTo(row); var credentialsBox = $('
').appendTo(row); var credentialsRightBox = $('
').appendTo(credentialsBox); var credentialsLeftBox = $('
').appendTo(credentialsBox); var credentialsEnabledBox = $('
').appendTo(credentialsLeftBox); $('').appendTo(credentialsEnabledBox); var credentialsDisabledBox = $('
').appendTo(credentialsLeftBox); $('').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 = $('
').appendTo(credentialsRightBox); $('').appendTo(row); // row = $('
').appendTo(credentialsRightBox); emptyProjectCredentialInput = $('').appendTo(row); emptyProjectCredentialInput.on("change keyup paste", validateForm); $('').appendTo(row); row = $('
').hide().appendTo(credentialsRightBox); $('
The credentials file will not be encrypted and its contents easily read
').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(); }) // Copy Project // row = $('
').appendTo(container); // $('').appendTo(row); // createProjectList({ // height: "250px", // small: true, // select: function(project) { // copyProject = project; // var projectName = projectNameInput.val(); // if (projectName === "" || projectName === autoInsertedName) { // autoInsertedName = project.name+"-copy"; // projectNameInput.val(autoInsertedName); // } // validateForm(); // } // }).appendTo(row); // Clone Project row = $('
').appendTo(container); $('').appendTo(row); projectRepoInput = $('').appendTo(row); $('').appendTo(row); var projectRepoChanged = false; projectRepoInput.on("change keyup paste",function() { projectRepoChanged = true; var repo = $(this).val(); var m = /\/([^/]+)\.git/.exec(repo); if (m) { var projectName = projectNameInput.val(); if (projectName === "" || projectName === autoInsertedName) { autoInsertedName = m[1]; projectNameInput.val(autoInsertedName); projectNameInput.change(); } } validateForm(); }); row = $('
').hide().appendTo(container); var subrow = $('
').appendTo(row); $('').appendTo(subrow); projectRepoUserInput = $('').appendTo(subrow); subrow = $('
').appendTo(row); $('').appendTo(subrow); projectRepoPasswordInput = $('').appendTo(subrow); row = $('
').hide().appendTo(container); $('').appendTo(row); projectRepoSSHKeySelect = createSSHKeyList({ height: "120px", selectAction: function(entry, header) { $('.projects-dialog-sshkey-list-entry').removeClass('selected'); header.addClass('selected'); } }).appendTo(row); row = $('
').hide().appendTo(container); $('').appendTo(row); projectRepoPassphrase = $('').appendTo(row); // row = $('
').hide().appendTo(container); // $('').appendTo(row); // projectRepoRemoteName = $('').val("origin").appendTo(row); // // row = $('
').hide().appendTo(container); // $('').appendTo(row); // projectRepoBranch = $('').val('master').appendTo(row); // // Secret - clone // row = $('
').appendTo(container); // $('').appendTo(row); // projectSecretInput = $('').appendTo(row); createAsEmpty.click(); setTimeout(function() { projectNameInput.focus(); },50); return container; }, buttons: [ { // id: "clipboard-dialog-cancel", text: RED._("common.label.cancel"), click: function() { $( this ).dialog( "close" ); } }, { id: "projects-dialog-create", text: "Create project", // TODO: nls 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') { var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val(); if (encryptionKeyType === 'custom') { projectData.credentialSecret = emptyProjectCredentialInput.val(); } else { // If 'use default', leave projectData.credentialSecret blank - as that will trigger // it to use the default (TODO: if its set...) } } else { // Disabled encryption by explicitly setting credSec to false projectData.credentialSecret = false; } } else if (projectType === 'copy') { projectData.copy = copyProject.name; } else if (projectType === 'clone') { // projectData.credentialSecret = projectSecretInput.val(); var repoUrl = projectRepoInput.val(); var metaData = {}; if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repoUrl)) { var selected = getSelectedSSHKey(projectRepoSSHKeySelect); if ( selected && selected.name ) { projectData.git = { remotes: { 'origin': { url: repoUrl, key_file: selected.name, 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() } } }; } } RED.deploy.setDeployInflight(true); RED.projects.settings.switchProject(projectData.name); sendRequest({ url: "projects", type: "POST", requireCleanWorkspace: true, 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"); }, 'git_auth_failed': function(error) { projectRepoUserInput.addClass("input-error"); projectRepoPasswordInput.addClass("input-error"); // getRepoAuthDetails(req); projectRepoSSHKeySelect.addClass("input-error"); projectRepoPassphrase.addClass("input-error"); console.log("git auth error",error); }, 'unexpected_error': function(error) { console.log("unexpected_error",error) } } } },projectData).always(function() { RED.deploy.setDeployInflight(false); }) // if (projectType === 'empty') { // show('credentialSecret'); // } else if (projectType === 'copy') { // show('copy'); // } else if (projectType === 'clone') { // show('clone'); // } // var projectName = projectNameInput.val().trim(); // var projectRepoEnabled = projectRepoEnabledInput.prop('checked'); // var projectRepo = projectRepoInput.val().trim(); // if (projectName !== '') { // var req = { // name:projectName // }; // if (projectRepoEnabled && projectRepo !== '') { // req.remote = projectRepo; // } // console.log(req); // sendRequest({ // url: "projects", // type: "POST", // responses: { // 200: function(data) { // console.log("Success!",data); // }, // 400: { // 'project_exists': function(error) { // console.log("already exists"); // }, // 'git_error': function(error) { // console.log("git error",error); // }, // 'git_auth_failed': function(error) { // // getRepoAuthDetails(req); // console.log("git auth error",error); // }, // 'unexpected_error': function(error) { // console.log("unexpected_error",error) // } // } // } // },req) // } // $( this ).dialog( "close" ); } } ] } })(), 'open': { content: function() { return createProjectList({ canSelectActive: false, dblclick: function() { $("#projects-dialog-open").click(); }, select: function() { $("#projects-dialog-open").prop('disabled',false).removeClass('disabled ui-button-disabled ui-state-disabled'); } }) }, buttons: [ { // id: "clipboard-dialog-cancel", text: RED._("common.label.cancel"), click: function() { $( this ).dialog( "close" ); } }, { id: "projects-dialog-open", text: "Open project", // TODO: nls class: "primary disabled", disabled: true, click: function() { switchProject(selectedProject.name,function(err,data) { if (err) { if (err.error === 'credentials_load_failed') { dialog.dialog( "close" ); } else { console.log("unexpected_error",err) } } else { dialog.dialog( "close" ); } }) } } ] }, 'delete': { content: function() { return createProjectList({ canSelectActive: false, dblclick: function() { $("#projects-dialog-delete").click(); }, select: function() { $("#projects-dialog-delete").prop('disabled',false).removeClass('disabled ui-button-disabled ui-state-disabled'); } }) }, buttons: [ { // id: "clipboard-dialog-cancel", text: RED._("common.label.cancel"), click: function() { $( this ).dialog( "close" ); } }, { id: "projects-dialog-delete", text: "Delete project", // TODO: nls class: "primary disabled", disabled: true, click: function() { deleteProject(selectedProject.name,function(err,data) { if (err) { if (err.error === 'credentials_load_failed') { dialog.dialog( "close" ); } else { console.log("unexpected_error",err) } } else { dialog.dialog( "close" ); } }) } } ] } } } 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: { 'credentials_load_failed': function(error) { done(error,null); }, 'unexpected_error': function(error) { done(error,null); } }, } },{active:true}).always(function() { RED.deploy.setDeployInflight(false); }) } function deleteProject(name,done) { sendRequest({ url: "projects/"+name, type: "DELETE", responses: { 200: function(data) { done(null,data); }, 400: { 'unexpected_error': function(error) { done(error,null); } } } }); } function show(s,options) { if (!dialog) { RED.projects.init(); } var screen = screens[s]; var container = screen.content(options); dialogBody.empty(); dialog.dialog('option','buttons',screen.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 }}); } var selectedProject = null; function createProjectList(options) { options = options||{}; var height = options.height || "300px"; selectedProject = null; var container = $('
',{style:"min-height: "+height+"; height: "+height+";"}); var list = $('
    ',{class:"projects-dialog-project-list", style:"height:"+height}).appendTo(container).editableList({ addButton: false, scrollOnAdd: false, addItem: function(row,index,entry) { var header = $('
    ',{class:"projects-dialog-project-list-entry"}).appendTo(row); $('').appendTo(header); $('').text(entry.name).appendTo(header); if (activeProject && activeProject.name === entry.name) { header.addClass("projects-list-entry-current"); $('current').appendTo(header); if (options.canSelectActive === false) { // active project cannot be selected; so skip the rest return } } header.addClass("selectable"); row.click(function(evt) { $('.projects-dialog-project-list-entry').removeClass('selected'); header.addClass('selected'); selectedProject = entry; if (options.select) { options.select(entry); } }) if (options.dblclick) { row.dblclick(function(evt) { evt.preventDefault(); options.dblclick(); }) } } }); if (options.small) { list.addClass("projects-dialog-project-list-small") } $.getJSON("projects", function(data) { data.projects.forEach(function(project) { list.editableList('addItem',{name:project}); }); }) return container; } function createSSHKeyList(options) { options = options || {}; var minHeight = "33px"; var maxHeight = options.height || "120px"; var container = $('
    ',{style:"max-height: "+maxHeight+";"}); var sshkeyList = $('
      ',{class:"projects-dialog-sshkey-list", style:"max-height:"+maxHeight+";min-height:"+minHeight+";"}).appendTo(container).editableList({ addButton: false, scrollOnAdd: false, addItem: function(row,index,entry) { var header = $('
      ',{class:"projects-dialog-sshkey-list-entry"}).appendTo(row); $('').appendTo(header); $('').text(entry.name).appendTo(header); var deleteButton = $('',{class:"projects-dialog-sshkey-list-entry-icon projects-dialog-sshkey-list-button-remove editor-button editor-button-small"}) .hide() .appendTo(header) .click(function(evt) { evt.preventDefault(); console.log('deleteButton --- click'); if ( options.deleteAction ) { options.deleteAction(entry, header); } return false; }); $('',{class:"fa fa-trash-o"}).appendTo(deleteButton); header.addClass("selectable"); row.click(function(evt) { if ( !deleteButton.is(":visible") ) { if ( options.selectAction ) { options.selectAction(entry, header); } $.data(container[0], 'selected', entry); } return false; }) } }); $.getJSON("settings/user/keys", function(data) { data.keys.forEach(function(key) { if ( sshkeyList ) { sshkeyList.editableList('addItem',key); } else { console.log('[create] Error! selectedSSHKey is not set up.'); } }); }); if ( sshkeyList ) { sshkeyList.addClass("projects-dialog-sshkey-list-small"); $.data(container[0], 'selected', null); $.data(container[0], 'sshkeys', sshkeyList); } return container; } function getSelectedSSHKey(container) { var selected = $.data(container[0], 'selected'); if ( container && selected ) { return selected; } else { return null; } } function refreshSSHKeyList(container) { var sshkeyList = $.data(container[0], 'sshkeys'); if ( container && sshkeyList ) { sshkeyList.empty(); $.getJSON("settings/user/keys", function(data) { var keyList = $.data(container[0], 'sshkeys'); data.keys.forEach(function(key) { if ( keyList ) { keyList.editableList('addItem',key); } else { console.log('[refresh] Error! selectedSSHKey is not set up.'); } }); }); } } function sendRequest(options,body) { // dialogBody.hide(); console.log(options.url,body); if (options.requireCleanWorkspace && RED.nodes.dirty()) { var message = 'You have undeployed changes that will be lost. Do you want to continue?'; var alwaysCallback; var cleanNotification = RED.notify(message,{ type:"info", fixed: true, modal: true, buttons: [ { //id: "node-dialog-delete", //class: 'leftButton', text: RED._("common.label.cancel"), click: function() { cleanNotification.close(); if (options.cancel) { options.cancel(); } if (alwaysCallback) { alwaysCallback(); } } },{ text: 'Continue', click: function() { cleanNotification.close(); delete options.requireCleanWorkspace; sendRequest(options,body).always(function() { if (alwaysCallback) { alwaysCallback(); } }) } } ] }); return { always: function(done) { alwaysCallback = done; } } } var start = Date.now(); // 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[options.remote||'origin'].fetch; var message = $('
      '+ '
      Authentication required for repository:
      '+ '
      '+url+'
      '+ '
      '+ '
      '+ '
      '); 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: $(' Retry'), click: function() { var username = $('#projects-user-auth-username').val(); var password = $('#projects-user-auth-password').val(); body = body || {}; var authBody = {git:{remotes:{}}}; authBody.git.remotes[options.remote||'origin'] = { username: username, password: password }; var done = function(err) { if (err) { console.log("Failed to update auth"); console.log(err); } else { sendRequest(options,body); notification.close(); } } sendRequest({ url: "projects/"+activeProject.name, type: "PUT", responses: { 0: function(error) { done(error,null); }, 200: function(data) { done(null,data); }, 400: { 'unexpected_error': function(error) { done(error,null); } }, } },authBody); } } ] }); return; } else if (responses[xhr.responseJSON.error]) { resultCallback = responses[xhr.responseJSON.error]; 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 = $('
      ').appendTo(options.container); var branchFilter = $('').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 = $("
        ",{style:"height: 130px;"}).appendTo(container); branchList.editableList({ addButton: false, scrollOnAdd: false, addItem: function(row,index,entry) { var container = $('
        ').appendTo(row); if (!entry.hasOwnProperty('commit')) { branchFilterCreateItem = container; $('').appendTo(container); $('').text("Create branch:").appendTo(container); $('
        ').text(entry.name).appendTo(container); } else { $('').appendTo(container); $('').text(entry.name).appendTo(container); if (entry.current) { container.addClass("selected"); $('').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: { 'unexpected_error': function(error) { console.log(error); } } } }) }, // addItem: function(data) { branchList.editableList('addItem',data) }, filter: function() { branchList.editableList('filter') }, focus: function() { branchFilter.focus() } } } function addSpinnerOverlay(container) { var spinner = $('
        ').appendTo(container); return spinner; } function init() { dialog = $('
        ') .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:delete-project",RED.projects.deleteProject); var projectsAPI = { sendRequest:sendRequest, createBranchList:createBranchList, addSpinnerOverlay:addSpinnerOverlay, createSSHKeyList:createSSHKeyList, refreshSSHKeyList:refreshSSHKeyList }; RED.projects.settings.init(projectsAPI); RED.projects.userSettings.init(projectsAPI); RED.sidebar.versionControl.init(projectsAPI); initScreens(); // initSidebar(); } function refresh(done) { $.getJSON("projects",function(data) { if (data.active) { $.getJSON("projects/"+data.active, function(project) { activeProject = project; // updateProjectSummary(); // updateProjectDescription(); // updateProjectDependencies(); RED.sidebar.versionControl.refresh(true); if (done) { done(activeProject); } }); } else { if (done) { done(null); } } }); } return { init: init, showStartup: function() { show('welcome'); }, newProject: function() { if (!activeProject) { show('welcome'); } else { show('create') } }, selectProject: function() { show('open') }, deleteProject: function() { show('delete') }, showCredentialsPrompt: function() { //TODO: rename this function RED.projects.settings.show('settings'); }, showFilesPrompt: function() { //TODO: rename this function RED.projects.settings.show('settings'); }, // showSidebar: showSidebar, refresh: refresh, editProject: function() { RED.projects.settings.show(); }, getActiveProject: function() { return activeProject; } } })();