/** * 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.settings = (function() { var trayWidth = 700; var settingsVisible = false; var panes = []; function addPane(options) { panes.push(options); } // TODO: DRY - tab-info.js function addTargetToExternalLinks(el) { $(el).find("a").each(function(el) { var href = $(this).attr('href'); if (/^https?:/.test(href)) { $(this).attr('target','_blank'); } }); return el; } function show(initialTab) { if (settingsVisible) { return; } if (!RED.user.hasPermission("projects.write")) { RED.notify(RED._("user.errors.notAuthorized"),"error"); return; } settingsVisible = true; var tabContainer; var trayOptions = { title: "Project Settings",// RED._("menu.label.userSettings"),, // TODO: nls buttons: [ { id: "node-dialog-ok", text: RED._("common.label.close"), class: "primary", click: function() { RED.tray.close(); } } ], resize: function(dimensions) { trayWidth = dimensions.width; }, open: function(tray) { var project = RED.projects.getActiveProject(); var trayBody = tray.find('.editor-tray-body'); var settingsContent = $('
').appendTo(trayBody); var tabContainer = $('
',{id:"user-settings-tabs-container"}).appendTo(settingsContent); $('',{id:"user-settings-tabs"}).appendTo(tabContainer); var settingsTabs = RED.tabs.create({ id: "user-settings-tabs", vertical: true, onchange: function(tab) { setTimeout(function() { $("#user-settings-tabs-content").children().hide(); $("#" + tab.id).show(); if (tab.pane.focus) { tab.pane.focus(); } },50); } }); var tabContents = $('
',{id:"user-settings-tabs-content"}).appendTo(settingsContent); panes.forEach(function(pane) { settingsTabs.addTab({ id: "project-settings-tab-"+pane.id, label: pane.title, pane: pane }); pane.get(project).hide().appendTo(tabContents); }); settingsContent.i18n(); settingsTabs.activateTab("project-settings-tab-"+(initialTab||'main')) $("#sidebar-shade").show(); }, close: function() { settingsVisible = false; panes.forEach(function(pane) { if (pane.close) { pane.close(); } }); $("#sidebar-shade").hide(); }, show: function() {} } if (trayWidth !== null) { trayOptions.width = trayWidth; } RED.tray.show(trayOptions); } function editDescription(activeProject, container) { RED.editor.editMarkdown({ title: RED._('sidebar.project.editDescription'), header: $(' README.md'), value: activeProject.description, complete: function(v) { container.empty(); var spinner = utils.addSpinnerOverlay(container); var done = function(err,res) { if (err) { return editDescription(activeProject, container); } activeProject.description = v; updateProjectDescription(activeProject, container); } utils.sendRequest({ url: "projects/"+activeProject.name, type: "PUT", responses: { 0: function(error) { done(error,null); }, 200: function(data) { done(null,data); RED.sidebar.versionControl.refresh(true); }, 400: { '*': function(error) { utils.reportUnexpectedError(error); done(error,null); } }, } },{description:v}).always(function() { spinner.remove(); }); } }); } function updateProjectDescription(activeProject, container) { container.empty(); var desc; if (activeProject.description) { desc = marked(activeProject.description); } else { desc = ''+'No description available'+''; } var description = addTargetToExternalLinks($(''+desc+'')).appendTo(container); description.find(".bidiAware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "" ); } function editSummary(activeProject, summary, container) { var editButton = container.prev(); editButton.hide(); container.empty(); var bg = $('').appendTo(container); var input = $('').val(summary||"").appendTo(container); $('') .appendTo(bg) .click(function(evt) { evt.preventDefault(); updateProjectSummary(activeProject.summary, container); editButton.show(); }); $('') .appendTo(bg) .click(function(evt) { evt.preventDefault(); var v = input.val(); updateProjectSummary(v, container); var spinner = utils.addSpinnerOverlay(container); var done = function(err,res) { if (err) { spinner.remove(); return editSummary(activeProject, summary, container); } activeProject.summary = v; spinner.remove(); updateProjectSummary(activeProject.summary, container); editButton.show(); } utils.sendRequest({ url: "projects/"+activeProject.name, type: "PUT", responses: { 0: function(error) { done(error,null); }, 200: function(data) { RED.sidebar.versionControl.refresh(true); done(null,data); }, 400: { '*': function(error) { utils.reportUnexpectedError(error); done(error,null); } }, } },{summary:v}); }); } function updateProjectSummary(summary, container) { container.empty(); if (summary) { container.text(summary).removeClass('node-info-node'); } else { container.text("No summary available").addClass('node-info-none');// TODO: nls } } function createMainPane(activeProject) { var pane = $('
'); $('

').text(activeProject.name).appendTo(pane); var summary = $('
').appendTo(pane); var summaryContent = $('
',{style:"color: #999"}).appendTo(summary); updateProjectSummary(activeProject.summary, summaryContent); if (RED.user.hasPermission("projects.write")) { $('') .prependTo(summary) .click(function(evt) { evt.preventDefault(); editSummary(activeProject, activeProject.summary, summaryContent); }); } $('
').appendTo(pane); var description = $('
').appendTo(pane); var descriptionContent = $('
',{style:"min-height: 200px"}).appendTo(description); updateProjectDescription(activeProject, descriptionContent); if (RED.user.hasPermission("projects.write")) { $('') .prependTo(description) .click(function(evt) { evt.preventDefault(); editDescription(activeProject, descriptionContent); }); } return pane; } function updateProjectDependencies(activeProject,depsList) { depsList.editableList('empty'); var totalCount = 0; var unknownCount = 0; var unusedCount = 0; var notInstalledCount = 0; for (var m in modulesInUse) { if (modulesInUse.hasOwnProperty(m)) { depsList.editableList('addItem',{ id: modulesInUse[m].module, version: modulesInUse[m].version, count: modulesInUse[m].count, known: activeProject.dependencies.hasOwnProperty(m), installed: true }); totalCount++; if (modulesInUse[m].count === 0) { unusedCount++; } if (!activeProject.dependencies.hasOwnProperty(m)) { unknownCount++; } } } if (activeProject.dependencies) { for (var m in activeProject.dependencies) { if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) { var installed = !!RED.nodes.registry.getModule(m); depsList.editableList('addItem',{ id: m, version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version, count: 0, known: true, installed: installed }); totalCount++; if (installed) { unusedCount++; } else { notInstalledCount++; } } } } // if (notInstalledCount > 0) { // depsList.editableList('addItem',{index:1, label:"Missing dependencies"}); // TODO: nls // } // if (unknownCount > 0) { // depsList.editableList('addItem',{index:1, label:"Unlisted dependencies"}); // TODO: nls // } // if (unusedCount > 0) { // depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls // } if (totalCount === 0) { depsList.editableList('addItem',{index:0, label:"None"}); // TODO: nls } } function saveDependencies(depsList,container,dependencies,complete) { var activeProject = RED.projects.getActiveProject(); var spinner = utils.addSpinnerOverlay(container).addClass('projects-dialog-spinner-contain'); var done = function(err,res) { spinner.remove(); if (err) { return complete(err); } activeProject.dependencies = dependencies; RED.sidebar.versionControl.refresh(true); complete(); } utils.sendRequest({ url: "projects/"+activeProject.name, type: "PUT", responses: { 0: function(error) { done(error,null); }, 200: function(data) { RED.sidebar.versionControl.refresh(true); done(null,data); }, 400: { '*': function(error) { done(error,null); } }, } },{dependencies:dependencies}); } function editDependencies(activeProject,depsJSON,container,depsList) { var json = depsJSON||JSON.stringify(activeProject.dependencies||{},"",4); if (json === "{}") { json = "{\n\n}"; } RED.editor.editJSON({ title: RED._('sidebar.project.editDependencies'), value: json, requireValid: true, complete: function(v) { try { var parsed = JSON.parse(v); saveDependencies(depsList,container,parsed,function(err) { if (err) { return editDependencies(activeProject,v,container,depsList); } activeProject.dependencies = parsed; updateProjectDependencies(activeProject,depsList); }); } catch(err) { editDependencies(activeProject,v,container,depsList); } } }); } function createDependenciesPane(activeProject) { var pane = $('
'); if (RED.user.hasPermission("projects.write")) { $('') .appendTo(pane) .click(function(evt) { evt.preventDefault(); editDependencies(activeProject,null,pane,depsList) }); } var depsList = $("
    ",{style:"position: absolute;top: 60px;bottom: 20px;left: 20px;right: 20px;"}).appendTo(pane); depsList.editableList({ addButton: false, addItem: function(row,index,entry) { // console.log(entry); var headerRow = $('
    ',{class:"palette-module-header"}).appendTo(row); if (entry.label) { if (entry.index === 0) { headerRow.addClass("red-ui-search-empty") } else { row.parent().addClass("palette-module-section"); } headerRow.text(entry.label); // if (RED.user.hasPermission("projects.write")) { // if (entry.index === 1) { // var addButton = $('').appendTo(headerRow).click(function(evt) { // evt.preventDefault(); // var deps = $.extend(true, {}, activeProject.dependencies); // for (var m in modulesInUse) { // if (modulesInUse.hasOwnProperty(m) && !modulesInUse[m].known) { // deps[m] = modulesInUse[m].version; // } // } // editDependencies(activeProject,JSON.stringify(deps,"",4),pane,depsList); // }); // } else if (entry.index === 3) { // var removeButton = $('').appendTo(headerRow).click(function(evt) { // evt.preventDefault(); // var deps = $.extend(true, {}, activeProject.dependencies); // for (var m in activeProject.dependencies) { // if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) { // delete deps[m]; // } // } // editDependencies(activeProject,JSON.stringify(deps,"",4),pane,depsList); // }); // } // } } else { headerRow.addClass("palette-module-header"); if (!entry.installed) { headerRow.addClass("palette-module-not-installed"); } else if (entry.count === 0) { headerRow.addClass("palette-module-unused"); } else if (!entry.known) { headerRow.addClass("palette-module-unknown"); } entry.element = headerRow; var titleRow = $('
    ').appendTo(headerRow); var iconClass = "fa-cube"; if (!entry.installed) { iconClass = "fa-warning"; } var icon = $('').appendTo(titleRow); entry.icon = icon; $('').html(entry.id).appendTo(titleRow); var metaRow = $('
    ').appendTo(headerRow); var versionSpan = $('').html(entry.version).appendTo(metaRow); metaRow = $('
    ').appendTo(headerRow); var buttons = $('
    ').appendTo(metaRow); if (RED.user.hasPermission("projects.write")) { if (!entry.installed && RED.settings.theme('palette.editable') !== false) { $('install').appendTo(buttons) .click(function(evt) { evt.preventDefault(); RED.palette.editor.install(entry,row,function(err) { if (!err) { entry.installed = true; var spinner = RED.utils.addSpinnerOverlay(row,true); setTimeout(function() { depsList.editableList('removeItem',entry); refreshModuleInUseCounts(); entry.count = modulesInUse[entry.id].count; depsList.editableList('addItem',entry); },500); } }); }) } else if (entry.known && entry.count === 0) { $('remove from project').appendTo(buttons) .click(function(evt) { evt.preventDefault(); var deps = $.extend(true, {}, activeProject.dependencies); delete deps[entry.id]; saveDependencies(depsList,row,deps,function(err) { if (!err) { row.fadeOut(200,function() { depsList.editableList('removeItem',entry); }); } else { console.log(err); } }); }); } else if (!entry.known) { $('add to project').appendTo(buttons) .click(function(evt) { evt.preventDefault(); var deps = $.extend(true, {}, activeProject.dependencies); deps[entry.id] = modulesInUse[entry.id].version; saveDependencies(depsList,row,deps,function(err) { if (!err) { buttons.remove(); headerRow.removeClass("palette-module-unknown"); } else { console.log(err); } }); }); } } } }, sort: function(A,B) { return A.id.localeCompare(B.id); // if (A.index && B.index) { // return A.index - B.index; // } // var Acategory = A.index?A.index:(A.known?(A.count>0?0:4):2); // var Bcategory = B.index?B.index:(B.known?(B.count>0?0:4):2); // if (Acategory === Bcategory) { // return A.id.localeCompare(B.id); // } else { // return Acategory - Bcategory; // } } }); updateProjectDependencies(activeProject,depsList); return pane; } function showProjectFileListing(row,activeProject,current,filter,done) { var dialog; var dialogBody; var filesList; var selected; var container = $('
    ',{style:"position: relative; min-height: 175px; height: 175px;"}).hide().appendTo(row); var spinner = utils.addSpinnerOverlay(container); $.getJSON("projects/"+activeProject.name+"/files",function(result) { var fileNames = Object.keys(result); fileNames = fileNames.filter(function(fn) { return !result[fn].status || !/D/.test(result[fn].status); }) var files = {}; fileNames.sort(); fileNames.forEach(function(file) { file.split("/").reduce(function(r,v,i,arr) { if (v) { if (i',{class:"projects-dialog-file-list", style:style}).appendTo(container).editableList({ addButton: false, scrollOnAdd: false, addItem: function(row,index,entry) { var header = $('
    ',{class:"projects-dialog-file-list-entry"}).appendTo(row); if (entry.children) { $(' ').appendTo(header); if (entry.children.length > 0) { var children = $('
    ',{style:"padding-left: 20px;"}).appendTo(row); if (current.indexOf(entry.path+"/") === 0) { header.addClass("expanded"); } else { children.hide(); } createFileSubList(children,entry.children,current,filter,onselect); header.addClass("selectable"); header.click(function(e) { if ($(this).hasClass("expanded")) { $(this).removeClass("expanded"); children.slideUp(200); } else { $(this).addClass("expanded"); children.slideDown(200); } }); } } else { var fileIcon = "fa-file-o"; var fileClass = ""; if (/\.json$/i.test(entry.name)) { fileIcon = "fa-file-code-o" } else if (/\.md$/i.test(entry.name)) { fileIcon = "fa-book"; } else if (/^\.git/i.test(entry.name)) { fileIcon = "fa-code-fork"; header.addClass("projects-dialog-file-list-entry-file-type-git"); } $(' ').appendTo(header); if (filter.test(entry.name)) { header.addClass("selectable"); if (entry.path === current) { header.addClass("selected"); } header.click(function(e) { $(".projects-dialog-file-list-entry.selected").removeClass("selected"); $(this).addClass("selected"); onselect(entry.path); }) header.dblclick(function(e) { e.preventDefault(); onselect(entry.path,true); }) } else { header.addClass("unselectable"); } } $('').text(entry.name).appendTo(header); } }); if (!style) { list.parent().css("overflow-y",""); } files.forEach(function(f) { list.editableList('addItem',f); }) } // function editFiles(activeProject, container,flowFile, flowFileLabel) { // var editButton = container.children().first(); // editButton.hide(); // // var flowFileInput = $('').val(flowFile).insertAfter(flowFileLabel); // // var flowFileInputSearch = $('') // .insertAfter(flowFileInput) // .click(function(e) { // showProjectFileListing(activeProject,'Select flow file',flowFileInput.val(),function(result) { // flowFileInput.val(result); // checkFiles(); // }) // }) // // var checkFiles = function() { // saveButton.toggleClass('disabled',flowFileInput.val()===""); // saveButton.prop('disabled',flowFileInput.val()===""); // } // flowFileInput.on("change keyup paste",checkFiles); // flowFileLabel.hide(); // // var bg = $('').prependTo(container); // $('') // .appendTo(bg) // .click(function(evt) { // evt.preventDefault(); // // flowFileLabel.show(); // flowFileInput.remove(); // flowFileInputSearch.remove(); // bg.remove(); // editButton.show(); // }); // var saveButton = $('') // .appendTo(bg) // .click(function(evt) { // evt.preventDefault(); // var newFlowFile = flowFileInput.val(); // var newCredsFile = credentialsFileInput.val(); // var spinner = utils.addSpinnerOverlay(container); // var done = function(err,res) { // if (err) { // spinner.remove(); // return; // } // activeProject.summary = v; // spinner.remove(); // flowFileLabel.text(newFlowFile); // flowFileLabel.show(); // flowFileInput.remove(); // flowFileInputSearch.remove(); // bg.remove(); // editButton.show(); // } // // utils.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); // // } // // }, // // } // // },{summary:v}); // }); // // // checkFiles(); // // } function createFilesSection(activeProject,pane) { var title = $('

    ').text("Files").appendTo(pane); var filesContainer = $('').appendTo(pane); if (RED.user.hasPermission("projects.write")) { var editFilesButton = $('') .appendTo(title) .click(function(evt) { evt.preventDefault(); formButtons.show(); editFilesButton.hide(); flowFileLabelText.hide(); flowFileInput.show(); flowFileInputSearch.show(); credFileLabel.hide(); credFileInput.show(); flowFileInput.focus(); // credentialStateLabel.parent().hide(); credentialStateLabel.addClass("uneditable-input"); $(".user-settings-row-credentials").show(); credentialStateLabel.css('height','auto'); credentialFormRows.hide(); credentialSecretButtons.show(); }); } var row; // Flow files row = $('').appendTo(filesContainer); $('').text('Flow').appendTo(row); var flowFileLabel = $('
    ').appendTo(row); var flowFileLabelText = $('').text(activeProject.files.flow).appendTo(flowFileLabel); var flowFileInput = $('').val(activeProject.files.flow).hide().appendTo(flowFileLabel); var flowFileInputSearch = $('') .hide() .appendTo(flowFileLabel) .click(function(e) { if ($(this).hasClass('selected')) { $(this).removeClass('selected'); flowFileLabel.find('.project-file-listing-container').slideUp(200,function() { $(this).remove(); flowFileLabel.css('height',''); }); flowFileLabel.css('color',''); } else { $(this).addClass('selected'); flowFileLabel.css('color','inherit'); var fileList = showProjectFileListing(flowFileLabel,activeProject,flowFileInput.val(), /.*\.json$/,function(result,isDblClick) { if (result) { flowFileInput.val(result); } if (isDblClick) { $(flowFileInputSearch).click(); } checkFiles(); }); flowFileLabel.css('height','auto'); setTimeout(function() { fileList.slideDown(200); },50); } }) row = $('').appendTo(filesContainer); $('').text('Credentials').appendTo(row); var credFileLabel = $('
    ').text(activeProject.files.credentials).appendTo(row); var credFileInput = $('
    ').text(activeProject.files.credentials).hide().insertAfter(credFileLabel); var checkFiles = function() { var saveDisabled; var currentFlowValue = flowFileInput.val(); var m = /^(.+?)(\.[^.]*)?$/.exec(currentFlowValue); if (m) { credFileInput.text(m[1]+"_cred"+(m[2]||".json")); } else if (currentFlowValue === "") { credFileInput.text(""); } var isFlowInvalid = currentFlowValue==="" || /\.\./.test(currentFlowValue) || /\/$/.test(currentFlowValue); saveDisabled = isFlowInvalid || credFileInput.text()===""; if (credentialSecretExistingInput.is(":visible")) { credentialSecretExistingInput.toggleClass("input-error", credentialSecretExistingInput.val() === ""); saveDisabled = saveDisabled || credentialSecretExistingInput.val() === ""; } if (credentialSecretNewInput.is(":visible")) { credentialSecretNewInput.toggleClass("input-error", credentialSecretNewInput.val() === ""); saveDisabled = saveDisabled || credentialSecretNewInput.val() === ""; } flowFileInput.toggleClass("input-error", isFlowInvalid); credFileInput.toggleClass("input-error",credFileInput.text()===""); saveButton.toggleClass('disabled',saveDisabled); saveButton.prop('disabled',saveDisabled); } flowFileInput.on("change keyup paste",checkFiles); if (!activeProject.files.flow) { $(' Missing').appendTo(flowFileLabelText); } if (!activeProject.files.credentials) { $(' Missing').appendTo(credFileLabel); } row = $('').appendTo(filesContainer); $('').appendTo(row); var credentialStateLabel = $(' ').appendTo(row); var credentialSecretButtons = $('').hide().appendTo(row); credentialStateLabel.css('color','#666'); credentialSecretButtons.css('vertical-align','top'); var credentialSecretResetButton = $('') .appendTo(credentialSecretButtons) .click(function(e) { e.preventDefault(); if (!$(this).hasClass('selected')) { credentialSecretNewInput.val(""); credentialSecretExistingRow.hide(); credentialSecretNewRow.show(); $(this).addClass("selected"); credentialSecretEditButton.removeClass("selected"); credentialResetLabel.show(); credentialResetWarning.show(); credentialSetLabel.hide(); credentialChangeLabel.hide(); credentialFormRows.show(); } else { $(this).removeClass("selected"); credentialFormRows.hide(); } checkFiles(); }); var credentialSecretEditButton = $('') .appendTo(credentialSecretButtons) .click(function(e) { e.preventDefault(); if (!$(this).hasClass('selected')) { credentialSecretExistingInput.val(""); credentialSecretNewInput.val(""); if (activeProject.settings.credentialSecretInvalid || !activeProject.settings.credentialsEncrypted) { credentialSetLabel.show(); credentialChangeLabel.hide(); credentialSecretExistingRow.hide(); } else { credentialSecretExistingRow.show(); credentialSetLabel.hide(); credentialChangeLabel.show(); } credentialSecretNewRow.show(); credentialSecretEditButton.addClass("selected"); credentialSecretResetButton.removeClass("selected"); credentialResetLabel.hide(); credentialResetWarning.hide(); credentialFormRows.show(); } else { $(this).removeClass("selected"); credentialFormRows.hide(); } checkFiles(); }) row = $('').hide().appendTo(filesContainer); var credentialFormRows = $('
    ',{style:"margin-top:10px"}).hide().appendTo(credentialStateLabel); var credentialSetLabel = $('
    Set the encryption key:
    ').hide().appendTo(credentialFormRows); var credentialChangeLabel = $('
    Change the encryption key:
    ').hide().appendTo(credentialFormRows); var credentialResetLabel = $('
    Reset the encryption key:
    ').hide().appendTo(credentialFormRows); var credentialSecretExistingRow = $('').appendTo(credentialFormRows); $('').text('Current key').appendTo(credentialSecretExistingRow); var credentialSecretExistingInput = $('').appendTo(credentialSecretExistingRow) .on("change keyup paste",function() { if (popover) { popover.close(); popover = null; } checkFiles(); }); var credentialSecretNewRow = $('').appendTo(credentialFormRows); $('').text('New key').appendTo(credentialSecretNewRow); var credentialSecretNewInput = $('').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles); var credentialResetWarning = $('
    This will delete all existing credentials
    ').hide().appendTo(credentialFormRows); var hideEditForm = function() { editFilesButton.show(); formButtons.hide(); flowFileLabelText.show(); flowFileInput.hide(); flowFileInputSearch.hide(); credFileLabel.show(); credFileInput.hide(); // credentialStateLabel.parent().show(); credentialStateLabel.removeClass("uneditable-input"); credentialStateLabel.css('height',''); flowFileInputSearch.removeClass('selected'); flowFileLabel.find('.project-file-listing-container').remove(); flowFileLabel.css('height',''); flowFileLabel.css('color',''); $(".user-settings-row-credentials").hide(); credentialFormRows.hide(); credentialSecretButtons.hide(); credentialSecretResetButton.removeClass("selected"); credentialSecretEditButton.removeClass("selected"); } var formButtons = $('').hide().appendTo(filesContainer); $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); hideEditForm(); }); var saveButton = $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); var spinner = utils.addSpinnerOverlay(filesContainer); var done = function(err) { spinner.remove(); if (err) { utils.reportUnexpectedError(err); return; } flowFileLabelText.text(flowFileInput.val()); credFileLabel.text(credFileInput.text()); hideEditForm(); } var payload = { files: { flow: flowFileInput.val(), credentials: credFileInput.text() } } if (credentialSecretResetButton.hasClass('selected')) { payload.resetCredentialSecret = true; } if (credentialSecretResetButton.hasClass('selected') || credentialSecretEditButton.hasClass('selected')) { payload.credentialSecret = credentialSecretNewInput.val(); if (credentialSecretExistingInput.is(":visible")) { payload.currentCredentialSecret = credentialSecretExistingInput.val(); } } // console.log(JSON.stringify(payload,null,4)); RED.deploy.setDeployInflight(true); utils.sendRequest({ url: "projects/"+activeProject.name, type: "PUT", responses: { 0: function(error) { done(error); }, 200: function(data) { activeProject = data; RED.sidebar.versionControl.refresh(true); updateForm(); done(); }, 400: { 'credentials_load_failed': function(error) { done(error); }, 'missing_current_credential_key': function(error) { credentialSecretExistingInput.addClass("input-error"); popover = RED.popover.create({ target: credentialSecretExistingInput, direction: 'right', size: 'small', content: "Incorrect key", autoClose: 3000 }).open(); done(error); }, '*': function(error) { done(error); } }, } },payload).always(function() { RED.deploy.setDeployInflight(false); }); }); var updateForm = function() { if (activeProject.settings.credentialSecretInvalid) { credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-warning"); credentialStateLabel.find(".user-settings-credentials-state").text("Invalid encryption key"); } else if (activeProject.settings.credentialsEncrypted) { credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-lock"); credentialStateLabel.find(".user-settings-credentials-state").text("Encryption enabled"); } else { credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-unlock"); credentialStateLabel.find(".user-settings-credentials-state").text("Encryption disabled"); } credentialSecretResetButton.toggleClass('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted); credentialSecretResetButton.prop('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted); } checkFiles(); updateForm(); } function createLocalBranchListSection(activeProject,pane) { var localBranchContainer = $('').appendTo(pane); $('

    ').text("Branches").appendTo(localBranchContainer); var row = $('').appendTo(localBranchContainer); var branchList = $('
      ').appendTo(row).editableList({ height: 'auto', addButton: false, scrollOnAdd: false, addItem: function(row,index,entry) { var container = $('
      ').appendTo(row); if (entry.empty) { container.addClass('red-ui-search-empty'); container.text("No branches"); return; } if (entry.current) { container.addClass("current"); } $('').appendTo(container); var content = $('').appendTo(container); var topRow = $('
      ').appendTo(content); $('').text(entry.name).appendTo(topRow); if (entry.commit) { $('').text(entry.commit.sha).appendTo(topRow); } if (entry.remote) { var bottomRow = $('
      ').appendTo(content); $('').text(entry.remote||"").appendTo(bottomRow); if (entry.status.ahead+entry.status.behind > 0) { $(''+ ' '+entry.status.ahead+' '+ ' '+entry.status.behind+''+ '').appendTo(bottomRow); } } if (!entry.current) { var tools = $('').appendTo(container); $('') .appendTo(tools) .click(function(e) { e.preventDefault(); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); var notification = RED.notify("Are you sure you want to delete the local branch '"+entry.name+"'? This cannot be undone.", { type: "warning", modal: true, fixed: true, buttons: [ { text: RED._("common.label.cancel"), click: function() { spinner.remove(); notification.close(); } },{ text: 'Delete branch', click: function() { notification.close(); var url = "projects/"+activeProject.name+"/branches/"+entry.name; var options = { url: url, type: "DELETE", responses: { 200: function(data) { row.fadeOut(200,function() { branchList.editableList('removeItem',entry); spinner.remove(); }); }, 400: { 'git_delete_branch_unmerged': function(error) { notification = RED.notify("The local branch '"+entry.name+"' has unmerged changes that will be lost. Are you sure you want to delete it?", { type: "warning", modal: true, fixed: true, buttons: [ { text: RED._("common.label.cancel"), click: function() { spinner.remove(); notification.close(); } },{ text: 'Delete unmerged branch', click: function() { options.url += "?force=true"; notification.close(); utils.sendRequest(options); } } ] }); }, '*': function(error) { utils.reportUnexpectedError(error); spinner.remove(); } }, } } utils.sendRequest(options); } } ] }) }) } } }); $.getJSON("projects/"+activeProject.name+"/branches",function(result) { if (result.branches) { if (result.branches.length > 0) { result.branches.sort(function(A,B) { if (A.current) { return -1 } if (B.current) { return 1 } return A.name.localeCompare(B.name); }); result.branches.forEach(function(branch) { branchList.editableList('addItem',branch); }) } else { branchList.editableList('addItem',{empty:true}); } } }) } function createRemoteRepositorySection(activeProject,pane) { $('

      ').text("Version Control").appendTo(pane); createLocalBranchListSection(activeProject,pane); var repoContainer = $('').appendTo(pane); var title = $('

      ').text("Git remotes").appendTo(repoContainer); var editRepoButton = $('') .appendTo(title) .click(function(evt) { editRepoButton.attr('disabled',true); addRemoteDialog.slideDown(200, function() { addRemoteDialog[0].scrollIntoView(); if (isEmpty) { remoteNameInput.val('origin'); remoteURLInput.focus(); } else { remoteNameInput.focus(); } validateForm(); }); }); var emptyItem = { empty: true }; var isEmpty = true; var row = $('').appendTo(repoContainer); var addRemoteDialog = $('
      ').hide().appendTo(row); row = $('').appendTo(repoContainer); var remotesList = $('
        ').appendTo(row); remotesList.editableList({ addButton: false, height: 'auto', addItem: function(row,index,entry) { var container = $('
        ').appendTo(row); if (entry.empty) { container.addClass('red-ui-search-empty'); container.text("No remotes"); return; } else { $('').appendTo(container); var content = $('').appendTo(container); $('
        ').text(entry.name).appendTo(content); if (entry.urls.fetch === entry.urls.push) { $('
        ').text(entry.urls.fetch).appendTo(content); } else { $('
        ').text("fetch: "+entry.urls.fetch).appendTo(content); $('
        ').text("push: "+entry.urls.push).appendTo(content); } var tools = $('').appendTo(container); $('') .appendTo(tools) .click(function(e) { e.preventDefault(); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); var notification = RED.notify("Are you sure you want to delete the remote '"+entry.name+"'?", { type: "warning", modal: true, fixed: true, buttons: [ { text: RED._("common.label.cancel"), click: function() { spinner.remove(); notification.close(); } },{ text: 'Delete remote', click: function() { notification.close(); var url = "projects/"+activeProject.name+"/remotes/"+entry.name; var options = { url: url, type: "DELETE", responses: { 200: function(data) { row.fadeOut(200,function() { remotesList.editableList('removeItem',entry); setTimeout(spinner.remove, 100); if (data.remotes.length === 0) { delete activeProject.git.remotes; isEmpty = true; remotesList.editableList('addItem',emptyItem); } else { activeProject.git.remotes = {}; data.remotes.forEach(function(remote) { var name = remote.name; delete remote.name; activeProject.git.remotes[name] = remote; }); } RED.sidebar.versionControl.refresh(); }); }, 400: { '*': function(error) { utils.reportUnexpectedError(error); spinner.remove(); } }, } } utils.sendRequest(options); } } ] }) }); } } }); var validateForm = function() { var validName = /^[a-zA-Z0-9\-_]+$/.test(remoteNameInput.val()); var repo = remoteURLInput.val(); // var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\.git)?(?:\/?|\#[\d\w\.\-_]+?)$/.test(remoteURLInput.val()); var validRepo = repo.length > 0 && !/\s/.test(repo); if (/^https?:\/\/[^/]+@/i.test(repo)) { remoteURLLabel.text("Do not include the username/password in the url"); validRepo = false; } else { remoteURLLabel.text("https://, ssh:// or file://"); } saveButton.attr('disabled',(!validName || !validRepo)) remoteNameInput.toggleClass('input-error',remoteNameInputChanged&&!validName); remoteURLInput.toggleClass('input-error',remoteURLInputChanged&&!validRepo); if (popover) { popover.close(); popover = null; } }; var popover; var remoteNameInputChanged = false; var remoteURLInputChanged = false; $('
        ').text('Add remote').appendTo(addRemoteDialog); row = $('').appendTo(addRemoteDialog); $('').text('Remote name').appendTo(row); var remoteNameInput = $('').appendTo(row).on("change keyup paste",function() { remoteNameInputChanged = true; validateForm(); }); $('').appendTo(row).find("small"); row = $('').appendTo(addRemoteDialog); $('').text('URL').appendTo(row); var remoteURLInput = $('').appendTo(row).on("change keyup paste",function() { remoteURLInputChanged = true; validateForm() }); var remoteURLLabel = $('').appendTo(row).find("small"); var hideEditForm = function() { editRepoButton.attr('disabled',false); addRemoteDialog.hide(); remoteNameInput.val(""); remoteURLInput.val(""); if (popover) { popover.close(); popover = null; } } var formButtons = $('') .appendTo(addRemoteDialog); $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); hideEditForm(); }); var saveButton = $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); var spinner = utils.addSpinnerOverlay(addRemoteDialog).addClass('projects-dialog-spinner-contain'); var payload = { name: remoteNameInput.val(), url: remoteURLInput.val() } var done = function(err) { spinner.remove(); if (err) { return; } hideEditForm(); } // console.log(JSON.stringify(payload,null,4)); RED.deploy.setDeployInflight(true); utils.sendRequest({ url: "projects/"+activeProject.name+"/remotes", type: "POST", responses: { 0: function(error) { done(error); }, 200: function(data) { activeProject.git.remotes = {}; data.remotes.forEach(function(remote) { var name = remote.name; delete remote.name; activeProject.git.remotes[name] = remote; }); updateForm(); RED.sidebar.versionControl.refresh(); done(); }, 400: { 'git_remote_already_exists': function(error) { popover = RED.popover.create({ target: remoteNameInput, direction: 'right', size: 'small', content: "Remote already exists", autoClose: 6000 }).open(); remoteNameInput.addClass('input-error'); done(error); }, '*': function(error) { utils.reportUnexpectedError(error); done(error); } }, } },payload); }); var updateForm = function() { remotesList.editableList('empty'); var count = 0; if (activeProject.git.hasOwnProperty('remotes')) { for (var name in activeProject.git.remotes) { if (activeProject.git.remotes.hasOwnProperty(name)) { count++; remotesList.editableList('addItem',{name:name,urls:activeProject.git.remotes[name]}); } } } isEmpty = (count === 0); if (isEmpty) { remotesList.editableList('addItem',emptyItem); } } updateForm(); } function createSettingsPane(activeProject) { var pane = $('
        '); createFilesSection(activeProject,pane); // createLocalRepositorySection(activeProject,pane); createRemoteRepositorySection(activeProject,pane); return pane; } function refreshModuleInUseCounts() { modulesInUse = {}; RED.nodes.eachNode(_updateModulesInUse); RED.nodes.eachConfig(_updateModulesInUse); } function _updateModulesInUse(n) { if (!/^subflow:/.test(n.type)) { var module = RED.nodes.registry.getNodeSetForType(n.type).module; if (module !== 'node-red') { if (!modulesInUse.hasOwnProperty(module)) { modulesInUse[module] = { module: module, version: RED.nodes.registry.getModule(module).version, count: 0, known: false } } modulesInUse[module].count++; } } } var popover; var utils; var modulesInUse = {}; function init(_utils) { utils = _utils; addPane({ id:'main', title: "Project", // TODO: nls get: createMainPane, close: function() { } }); addPane({ id:'deps', title: "Dependencies", // TODO: nls get: createDependenciesPane, close: function() { } }); addPane({ id:'settings', title: "Settings", // TODO: nls get: createSettingsPane, close: function() { if (popover) { popover.close(); popover = null; } } }); RED.events.on('nodes:add', _updateModulesInUse); RED.events.on('nodes:remove', function(n) { if (!/^subflow:/.test(n.type)) { var module = RED.nodes.registry.getNodeSetForType(n.type).module; if (module !== 'node-red' && modulesInUse.hasOwnProperty(module)) { modulesInUse[module].count--; if (modulesInUse[module].count === 0) { if (!modulesInUse[module].known) { delete modulesInUse[module]; } } } } }) } return { init: init, show: show, switchProject: function(name) { // TODO: not ideal way to trigger this; should there be an editor-wide event? modulesInUse = {}; } }; })();