/** * 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("
You Git client is not configured with a username/email.
",{ 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("An unexpected error occurred:
"+error.message+"
code: "+error.error+"",{ fixed: true, modal: true, type: 'error', buttons: [ { text: "Close", click: function() { notification.close(); } } ] }) } } var screens = {}; function initScreens() { var migrateProjectHeader = $(''); $(' ').appendTo(migrateProjectHeader) $('').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); 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 = $('
'); migrateProjectHeader.appendTo(container); var body = $('').appendTo(container); $('').text("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); if (isGlobalConfig) { $('
').text("Your Git client is already configured with the details below.").appendTo(body); } $('
').text("You can change these settings later under the 'Git config' tab of the settings dialog.").appendTo(body); var row = $('
').appendTo(body); $('').appendTo(row); gitUsernameInput = $('').val((existingGitSettings&&existingGitSettings.name)||"").appendTo(row); // $('').text("This does not need to be your real name").appendTo(row); gitUsernameInput.on("change keyup paste",validateForm); row = $('').appendTo(body); $('').appendTo(row); gitEmailInput = $('').val((existingGitSettings&&existingGitSettings.email)||"").appendTo(row); gitEmailInput.on("change keyup paste",validateForm); // $('').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 = $(''); migrateProjectHeader.appendTo(container); var body = $('').appendTo(container); $('').text("Create your project").appendTo(body); $('
').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); $('
').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) { 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; $("#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(); 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 = $(''); migrateProjectHeader.appendTo(container); var body = $('').appendTo(container); $('').text("Create your project files").appendTo(body); $('
').text("A project contains your flow files, a README file, a package.json file and a settings file.").appendTo(body); $('
').text("It can contain any other files you want to maintain in the Git repository.").appendTo(body); if (!options.existingProject) { $('
').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(''); } projectCredentialFileInput.text(""); if (!projectCredentialFileInput.hasClass("input-error")) { projectCredentialFileInput.addClass("input-error"); projectCredentialFileInput.next().empty().append(''); } } 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 = $('
').appendTo(body); $('').appendTo(row); var subrow = $('').appendTo(row); var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || RED.settings.files.flow||"flow.json"; projectFlowFileInput = $('').val(defaultFlowFile) .on("change keyup paste",validateForm) .appendTo(subrow); $('').appendTo(subrow); $('').appendTo(row); var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || RED.settings.files.credentials||"flow_cred.json"; row = $('').appendTo(body); $('').appendTo(row); subrow = $('').appendTo(row); projectCredentialFileInput = $('').text("Setup encryption of your credentials file").appendTo(body); if (options.existingProject) { $('
').text("Your flow credentials file can be encrypted to keep its contents secure.").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 === '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 key. You should provide a new secret key for this project.").appendTo(body); } $('
').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 = $('
').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 = $('').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 '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; 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 = $('
'); 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 = /^(?: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(''); } } 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 = $('').appendTo(container); var openProject = $('').appendTo(row); var createAsEmpty = $('').appendTo(row); // var createAsCopy = $('').appendTo(row); var createAsClone = $('').appendTo(row); // var createAsClone = $('').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 = $('').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 = $('').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 = $('