diff --git a/src/main/resources/hudson/security/table.js b/src/main/resources/hudson/security/table.js index 65e2014..7909187 100644 --- a/src/main/resources/hudson/security/table.js +++ b/src/main/resources/hudson/security/table.js @@ -1,41 +1,47 @@ +/* global Behaviour, dialog, FormChecker, findElementsBySelector, findAncestor */ + +function matrixAuthEscapeHtml(html) { + return html.replace(/'/g, "'").replace(/"/g, """).replace(/&/g, "&").replace(//g, ">"); +} + /* * This handles the addition of new users/groups to the list. */ Behaviour.specify(".matrix-auth-add-button", 'GlobalMatrixAuthorizationStrategy', 0, function(e) { e.onclick = function (e) { - var dataReference = e.target; - var master = document.getElementById(dataReference.getAttribute('data-table-id')); - var table = master.parentNode; - var type = dataReference.getAttribute('data-type'); - var typeLabel = dataReference.getAttribute('data-type-label'); + const dataReference = e.target; + const master = document.getElementById(dataReference.getAttribute('data-table-id')); + const table = master.parentNode; + const type = dataReference.getAttribute('data-type'); + const typeLabel = dataReference.getAttribute('data-type-label'); - var name = dialog.prompt(dataReference.getAttribute('data-message-title'), { + dialog.prompt(dataReference.getAttribute('data-message-title'), { message: dataReference.getAttribute('data-message-prompt') }).then ((name) => { - if(findElementsBySelector(table,"TR").find(function(n){return n.getAttribute("name")=='['+type+':'+name+']';})!=null) { + if (findElementsBySelector(table, "TR").find(function(n){ + return n.getAttribute("name") === '[' + type + ':' + name + ']'; + }) != null) { dialog.alert(dataReference.getAttribute('data-message-error')); return; } - if(document.importNode!=null) - copy = document.importNode(master,true); - else - copy = master.cloneNode(true); // for IE + const copy = document.importNode(master, true); copy.removeAttribute("id"); copy.removeAttribute("style"); - copy.firstChild.innerHTML = YAHOO.lang.escapeHTML(name); // TODO consider setting innerText - copy.setAttribute("name",'['+type+':'+name+']'); + copy.firstChild.innerHTML = matrixAuthEscapeHtml(name); // TODO consider setting innerText + copy.setAttribute("name", '[' + type + ':' + name + ']'); - for(var child = copy.firstChild; child !== null; child = child.nextSibling) { + for (let child = copy.firstChild; child !== null; child = child.nextSibling) { if (child.hasAttribute('data-permission-id')) { child.setAttribute("data-tooltip-enabled", child.getAttribute("data-tooltip-enabled").replace("__SID__", name).replace("__TYPE__", typeLabel)); child.setAttribute("data-tooltip-disabled", child.getAttribute("data-tooltip-disabled").replace("__SID__", name).replace("__TYPE__", typeLabel)); } } - var tooltipAttributeName = getTooltipAttributeName(); + const tooltipAttributeName = 'data-html-tooltip'; findElementsBySelector(copy, ".stop a").forEach(function(item) { + // TODO Clean this up, `title` should be long obsolete. let oldTitle = item.getAttribute("title"); if (oldTitle !== null) { item.setAttribute("title", oldTitle.replace("__SID__", name).replace("__TYPE__", typeLabel)); @@ -44,15 +50,16 @@ Behaviour.specify(".matrix-auth-add-button", 'GlobalMatrixAuthorizationStrategy' }); findElementsBySelector(copy, "input[type=checkbox]").forEach(function(item) { - const tooltip = item.getAttribute(tooltipAttributeName); + const tooltip = item.nextSibling.getAttribute(tooltipAttributeName); if (tooltip) { - item.setAttribute(tooltipAttributeName, tooltip.replace("__SID__", name).replace("__TYPE__", typeLabel)); + item.nextSibling.setAttribute(tooltipAttributeName, tooltip.replace("__SID__", name).replace("__TYPE__", typeLabel)); } else { - item.setAttribute("title", item.getAttribute("title").replace("__SID__", name).replace("__TYPE__", typeLabel)); + // TODO Clean this up, `title` should be long obsolete. + item.nextSibling.setAttribute("title", item.getAttribute("title").replace("__SID__", name).replace("__TYPE__", typeLabel)); } }); table.appendChild(copy); - Behaviour.applySubtree(findAncestor(table,"TABLE"),true); + Behaviour.applySubtree(findAncestor(table, "TABLE"), true); }, () => {}); } }); @@ -64,23 +71,23 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.remove" e.onclick = function() { // Run ambiguity warning removal code: If all ambiguous rows are deleted, the warning needs to go as well // Order of operations: Find table ancestor, remove row, iterate over leftover rows - var table = findAncestor(this,"TABLE"); + const table = findAncestor(this, "TABLE"); - var tr = findAncestor(this,"TR"); + const tr = findAncestor(this, "TR"); tr.parentNode.removeChild(tr); - var tableRows = table.getElementsByTagName('tr'); + const tableRows = table.getElementsByTagName('tr'); - var hasAmbiguousRows = false; + let hasAmbiguousRows = false; - for (var i = 0; i < tableRows.length; i++) { + for (let i = 0; i < tableRows.length; i++) { if (tableRows[i].getAttribute('name') !== null && tableRows[i].getAttribute('name').startsWith('[EITHER')) { hasAmbiguousRows = true; } } if (!hasAmbiguousRows) { - var alertElements = document.getElementsByClassName("alert"); - for (var i = 0; i < alertElements.length; i++) { + const alertElements = document.getElementsByClassName("alert"); + for (let i = 0; i < alertElements.length; i++) { if (alertElements[i].hasAttribute('data-table-id') && alertElements[i].getAttribute('data-table-id') === table.getAttribute('data-table-id')) { alertElements[i].style.display = 'none'; // TODO animate this? } @@ -89,7 +96,6 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.remove" return false; } - e = null; // avoid memory leak }); /* @@ -97,15 +103,16 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.remove" */ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.selectall", 'GlobalMatrixAuthorizationStrategy', 0, function(e) { e.onclick = function() { - var tr = findAncestor(this,"TR"); - var inputs = tr.getElementsByTagName("INPUT"); - for(var i=0; i < inputs.length; i++){ - if(inputs[i].type == "checkbox") inputs[i].checked = true; + const tr = findAncestor(this, "TR"); + const inputs = tr.getElementsByTagName("INPUT"); + for (let i=0; i < inputs.length; i++) { + if (inputs[i].type === "checkbox") { + inputs[i].checked = true; + } } - Behaviour.applySubtree(findAncestor(this,"TABLE"),true); + Behaviour.applySubtree(findAncestor(this, "TABLE"), true); return false; }; - e = null; // avoid memory leak }); /* @@ -113,15 +120,16 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.selecta */ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.unselectall", 'GlobalMatrixAuthorizationStrategy', 0, function(e) { e.onclick = function() { - var tr = findAncestor(this,"TR"); - var inputs = tr.getElementsByTagName("INPUT"); - for(var i=0; i < inputs.length; i++){ - if(inputs[i].type == "checkbox") inputs[i].checked = false; + const tr = findAncestor(this, "TR"); + const inputs = tr.getElementsByTagName("INPUT"); + for (let i = 0; i < inputs.length; i++) { + if (inputs[i].type === "checkbox") { + inputs[i].checked = false; + } } - Behaviour.applySubtree(findAncestor(this,"TABLE"),true); + Behaviour.applySubtree(findAncestor(this, "TABLE"), true); return false; }; - e = null; // avoid memory leak }); /* @@ -129,18 +137,18 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.unselec */ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.migrate", 'GlobalMatrixAuthorizationStrategy', 0, function(e) { e.onclick = function() { - var tr = findAncestor(this,"TR"); - var name = tr.getAttribute('name'); + const tr = findAncestor(this, "TR"); + const name = tr.getAttribute('name'); - var newName = name.replace('[EITHER:', '[USER:'); // migrate_user behavior + let newName = name.replace('[EITHER:', '[USER:'); // migrate_user behavior if (this.classList.contains('migrate_group')) { newName = name.replace('[EITHER:', '[GROUP:'); } - var table = findAncestor(this,"TABLE"); - var tableRows = table.getElementsByTagName('tr'); - var newNameElement = null; - for (var i = 0; i < tableRows.length; i++) { + const table = findAncestor(this, "TABLE"); + const tableRows = table.getElementsByTagName('tr'); + let newNameElement = null; + for (let i = 0; i < tableRows.length; i++) { if (tableRows[i].getAttribute('name') === newName) { newNameElement = tableRows[i]; break; @@ -156,19 +164,19 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.migrate tr.removeAttribute('data-checked'); // remove migration buttons from updated row - var buttonContainer = findAncestor(this, "DIV"); - var migrateButtons = buttonContainer.getElementsByClassName('migrate'); - for (var i = migrateButtons.length - 1; i >= 0; i--) { + const buttonContainer = findAncestor(this, "DIV"); + const migrateButtons = buttonContainer.getElementsByClassName('migrate'); + for (let i = migrateButtons.length - 1; i >= 0; i--) { buttonContainer.removeChild(migrateButtons[i]); } } else { // there's already a row for the migrated name (unusual but OK), so merge them // migrate permissions from this row - var ambiguousPermissionInputs = tr.getElementsByTagName("INPUT"); - var unambiguousPermissionInputs = newNameElement.getElementsByTagName("INPUT"); - for (var i = 0; i < ambiguousPermissionInputs.length; i++){ - if(ambiguousPermissionInputs[i].type == "checkbox") { + const ambiguousPermissionInputs = tr.getElementsByTagName("INPUT"); + const unambiguousPermissionInputs = newNameElement.getElementsByTagName("INPUT"); + for (let i = 0; i < ambiguousPermissionInputs.length; i++){ + if (ambiguousPermissionInputs[i].type === "checkbox") { unambiguousPermissionInputs[i].checked |= ambiguousPermissionInputs[i].checked; } newNameElement.classList.add('highlight-entry'); @@ -179,16 +187,16 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.migrate } Behaviour.applySubtree(table, true); - var hasAmbiguousRows = false; + let hasAmbiguousRows = false; - for (var i = 0; i < tableRows.length; i++) { + for (let i = 0; i < tableRows.length; i++) { if (tableRows[i].getAttribute('name') !== null && tableRows[i].getAttribute('name').startsWith('[EITHER')) { hasAmbiguousRows = true; } } if (!hasAmbiguousRows) { - var alertElements = document.getElementsByClassName("alert"); - for (var i = 0; i < alertElements.length; i++) { + let alertElements = document.getElementsByClassName("alert"); + for (let i = 0; i < alertElements.length; i++) { if (alertElements[i].hasAttribute('data-table-id') && alertElements[i].getAttribute('data-table-id') === table.getAttribute('data-table-id')) { alertElements[i].style.display = 'none'; // TODO animate this? } @@ -197,64 +205,43 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TD.stop A.migrate return false; }; - e = null; // avoid memory leak }); -/* - * Determine which attribute to set tooltips in. Changed in Jenkins 2.379 with Tippy and data-html-tooltip support. - */ -function getTooltipAttributeName() { - let coreVersion = document.body.getAttribute('data-version'); - if (coreVersion === null) { - return 'tooltip' - } - // TODO remove after minimum version is 2.379 or higher - let tippySupported = coreVersion >= '2.379'; - return tippySupported ? 'data-html-tooltip' : 'tooltip'; -} - /* * Whenever permission assignments change, this ensures that implied permissions get their checkboxes disabled. */ Behaviour.specify(".global-matrix-authorization-strategy-table td input", 'GlobalMatrixAuthorizationStrategy', 0, function(e) { - var table = findAncestor(e, "TABLE"); + const table = findAncestor(e, "TABLE"); if (table.classList.contains('read-only')) { // if this is a read-only UI (ExtendedRead / SystemRead), do not enable checkboxes return; } - var tooltipAttributeName = getTooltipAttributeName(); + const tooltipAttributeName = 'data-html-tooltip'; - var impliedByString = findAncestor(e, "TD").getAttribute('data-implied-by-list'); - var impliedByList = impliedByString.split(" "); - var tr = findAncestor(e,"TR"); + const impliedByString = findAncestor(e, "TD").getAttribute('data-implied-by-list'); + const impliedByList = impliedByString.split(" "); + const tr = findAncestor(e, "TR"); e.disabled = false; - let tooltip = YAHOO.lang.escapeHTML(findAncestor(e, "TD").getAttribute('data-tooltip-enabled')); - e.setAttribute(tooltipAttributeName, tooltip); // before 2.335 -- TODO remove once baseline is new enough - e.nextSibling.setAttribute(tooltipAttributeName, tooltip); // 2.335+ + let tooltip = matrixAuthEscapeHtml(findAncestor(e, "TD").getAttribute('data-tooltip-enabled')); + e.nextSibling.setAttribute(tooltipAttributeName, tooltip); - for (var i = 0; i < impliedByList.length; i++) { - var permissionId = impliedByList[i]; - var reference = tr.querySelector("td[data-permission-id='" + permissionId + "'] input"); + for (let i = 0; i < impliedByList.length; i++) { + let permissionId = impliedByList[i]; + let reference = tr.querySelector("td[data-permission-id='" + permissionId + "'] input"); if (reference !== null) { if (reference.checked) { e.disabled = true; - let tooltip = YAHOO.lang.escapeHTML(findAncestor(e, "TD").getAttribute('data-tooltip-disabled')); - e.setAttribute(tooltipAttributeName, tooltip); // before 2.335 -- TODO remove once baseline is new enough - e.nextSibling.setAttribute(tooltipAttributeName, tooltip); // 2.335+ + let tooltip = matrixAuthEscapeHtml(findAncestor(e, "TD").getAttribute('data-tooltip-disabled')); + e.nextSibling.setAttribute(tooltipAttributeName, tooltip); } } } - if (window.registerTooltips) { - window.registerTooltips(e.nextSibling.parentElement); - } - e.onchange = function() { - Behaviour.applySubtree(findAncestor(this,"TABLE"),true); + Behaviour.applySubtree(findAncestor(this, "TABLE"), true); return true; }; - e = null; // avoid memory leak }); /* @@ -265,7 +252,7 @@ Behaviour.specify(".global-matrix-authorization-strategy-table TR.permission-row return; } if (!e.hasAttribute('data-checked')) { - FormChecker.delayedCheck(e.getAttribute('data-descriptor-url') + "/checkName?value="+encodeURIComponent(e.getAttribute("name")),"GET",e.firstChild); + FormChecker.delayedCheck(e.getAttribute('data-descriptor-url') + "/checkName?value=" + encodeURIComponent(e.getAttribute("name")), "GET", e.firstChild); e.setAttribute('data-checked', 'true'); } });