frontend/janus.js (259 lines of code) (raw):
import DOMPurify from 'dompurify';
import M from 'materialize-css';
import {setUpProtectedLinks, setUpRegisterPasskeyButton} from './passkeys.js';
document.addEventListener('DOMContentLoaded', function() {
"use strict";
//Initialise Materialize elements
const sidenavElems = document.querySelectorAll('.sidenav');
// eslint-disable-next-line no-unused-vars -- required by Materialize
const sidenavInstances = M.Sidenav.init(sidenavElems);
const modalElems = document.querySelectorAll('.modal');
// eslint-disable-next-line no-unused-vars -- required by Materialize
const modalInstances = M.Modal.init(modalElems);
M.updateTextFields();
const collapsibleElems = document.querySelectorAll('.collapsible');
// eslint-disable-next-line no-unused-vars -- required by Materialize
const collapsibleInstances = M.Collapsible.init(collapsibleElems);
const dropdownElems = document.querySelectorAll('.dropdown-trigger');
// eslint-disable-next-line no-unused-vars -- required by Materialize
const dropdownInstances = M.Dropdown.init(dropdownElems);
const tooltipElems = document.querySelectorAll('.tooltipped');
// eslint-disable-next-line no-unused-vars -- required by Materialize
const instances = M.Tooltip.init(tooltipElems);
// aws-profile-name
if (document.querySelectorAll('.editable-aws-profile').length) {
const profileIdContainers = document.querySelectorAll('.editable-aws-profile .aws-profile-id');
document.querySelectorAll("#aws-profile-id").forEach(function(input){
input.addEventListener('keyup', function() {
profileIdContainers.forEach(function(el) {
let lines;
if (el.tagName === "TEXTAREA") {
lines = el.value.split("\n");
} else {
lines = el.innerText.split("\n");
}
const replaced = lines.map(function(line) {
return line.replace(/--profile [a-zA-Z0-9-]*/, "--profile " + input.value);
});
if (el.tagName === "TEXTAREA") {
el.value = replaced.join("\n");
} else {
el.innerText = replaced.join("\n");
}
});
});
});
}
// copy-text
document.querySelectorAll(".copy-text--button").forEach(function(button) {
const container = button.closest(".copy-textarea"),
textAreaTarget = container.querySelector("textarea"),
defaultCopyTextIcon = container.querySelector(".copy-text--default"),
confirmationIcon = container.querySelector(".copy-text--confirm"),
warnIcon = container.querySelector(".copy-text--warn");
button.addEventListener('click', function(e) {
e.preventDefault();
try {
textAreaTarget.select();
navigator.clipboard.writeText(textAreaTarget.value).then(function() {
confirmationIcon.style.display = "inline";
defaultCopyTextIcon.style.display = "none";
setTimeout(function() {
confirmationIcon.style.display = "none";
warnIcon.style.display = "none";
defaultCopyTextIcon.style.display = "inline";
}, 4000);
}).catch(function(err) {
warnIcon.style.display = "inline";
defaultCopyTextIcon.style.display = "none";
throw(err);
});
} catch (err) {
warnIcon.style.display = "inline";
defaultCopyTextIcon.style.display = "none";
throw(err);
}
});
});
// index page controls are fixed until user scrolls to footer
document.querySelectorAll('.controls__hero').forEach(function() {
const win = window,
controlContainer = document.querySelector('.controls__hero'),
footer = document.querySelector('footer'),
recalculate = function() {
const lowestVisiblePixel = win.scrollY + win.innerHeight,
footerPosition = footer.getBoundingClientRect().top + win.scrollY;
if (lowestVisiblePixel > footerPosition) {
controlContainer.style.position = "relative";
footer.style.marginTop = 0;
} else {
controlContainer.style.position = "fixed";
footer.style.marginTop = controlContainer.offsetHeight + "px";
}
};
// enable feature if JS is available
controlContainer.style.display = "block";
win.addEventListener('scroll', recalculate);
win.addEventListener('resize', recalculate);
recalculate();
});
// allow user to simultaneously obtain credentials from multiple accounts
document.querySelectorAll(".multiple-credentials-control__container").forEach(function(container) {
const checkboxes = document.querySelectorAll(".multi-select__checkbox"),
singleCredentialsLinks = document.querySelectorAll(".federation__link--credentials"),
activeContainer = document.querySelector(".multiple-credentials-control--active"),
inactiveContainer = document.querySelector(".multiple-credentials-control--inactive"),
controlFeedbackText = document.querySelector(".multi-accounts-count"),
accountContainers = document.querySelectorAll(".card--aws-account"),
clearButton = document.querySelector(".multiple-credentials-control--clear"),
updateLink = function(permissions) {
const link = document.querySelector(".multiple-credentials__link"),
hasPermissions = link.href.indexOf("permissionIds=") !== -1,
permissionStr = permissions.map(encodeURIComponent).join(",");
if (hasPermissions) {
link.href = link.href.replace(/permissionIds=[^&]*/, "permissionIds=" + permissionStr);
} else {
link.href += "&permissionIds=" + permissionStr;
}
},
updateSelection = function(event) {
const checked = Array.from(checkboxes).filter(function(checkbox) {
return checkbox.checked;
}),
permissions = checked.map(function(checkbox) {
return DOMPurify.sanitize(checkbox.getAttribute("data-permission-id"));
}),
clicked = event ? event.target : null,
accountContainer = clicked ? clicked.closest(".aws-account-body") : null,
otherAccountCheckboxes = accountContainer ? accountContainer.querySelectorAll(".multi-select__checkbox:not(:checked)") : [];
// enable feature if JS is available
document.querySelectorAll(".multi-select__container").forEach(el => el.style.display = "block");
// deal with disabling other permissions in the same account as the one just selected
if (event) {
if (clicked.checked) {
otherAccountCheckboxes.forEach(function(checkbox) {
checkbox.checked = false;
checkbox.disabled = true;
});
} else {
otherAccountCheckboxes.forEach(function(checkbox) {
checkbox.checked = false;
checkbox.disabled = false;
});
}
}
// update display
if (permissions.length) {
activeContainer.style.display = "block";
inactiveContainer.style.display = "none";
// disable all non-multi credentials links to avoid confusion
singleCredentialsLinks.forEach(function(link) {
link.classList.add('disabled');
});
accountContainers.forEach(function(container) {
container.style.filter = "grayscale(0)";
});
Array.from(accountContainers).filter(function(container) {
return !checked.some(function(checkbox) {
return container.contains(checkbox);
});
}).forEach(function(container) {
container.style.filter = "grayscale(0.6)";
});
} else {
inactiveContainer.style.display = "block";
activeContainer.style.display = "none";
// restore single credentials links
singleCredentialsLinks.forEach(function(link) {
link.classList.remove('disabled');
});
accountContainers.forEach(function(container) {
container.style.filter = "grayscale(0)";
});
}
controlFeedbackText.textContent = permissions.length === 1 ? permissions.length + " account" : permissions.length + " accounts";
updateLink(permissions);
};
container.style.display = "block";
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', updateSelection);
});
clearButton.addEventListener('click', function() {
checkboxes.forEach(function(checkbox) {
checkbox.checked = false;
checkbox.disabled = false;
});
updateSelection();
});
updateSelection();
});
// add timezone to federation links
document.querySelectorAll(".federation__link").forEach(function(el) {
const link = el,
tzOffset = (new Date().getTimezoneOffset() * -1) / 60;
link.href = link.href + "&tzOffset=" + tzOffset;
});
// login lease time
document.querySelectorAll(".login-duration__container").forEach(function(container) {
const defaultLongDurationLink = document.querySelector(".dropdown-time__link--default[data-length=standard]"),
links = container.querySelectorAll(".time-link"),
maxLongDuration = document.querySelector(".dropdown-time__link--max[data-length=standard]").dataset.duration,
endOfWorkSeconds = (function() {
let ms,
endOfWork = new Date();
endOfWork.setHours(19);
endOfWork.setMinutes(0);
endOfWork.setSeconds(0);
ms = endOfWork.getTime();
if (Date.now() - endOfWork > 0) {
return ms + 86400 * 1000;
} else {
return ms;
}
})(),
msToEndOfWork = endOfWorkSeconds - Date.now(),
updateHrefDurations = function(duration, shortTerm) {
const selector = ".federation__link--" + (shortTerm ? "short" : "standard");
document.querySelectorAll(selector).forEach(function(link) {
const hasDuration = link.href.indexOf("duration=") !== -1;
if (hasDuration) {
link.href = link.href.replace(/duration=[^&]*/, "duration=" + duration);
} else {
link.href = link.href + "&duration=" + duration;
}
});
};
// enable feature if JS is available
links.forEach(function(link) {
link.removeAttribute("disabled");
});
// add click handlers to time choices
document.querySelectorAll(".dropdown-time__link").forEach(function(link) {
link.addEventListener('click', function(e) {
e.preventDefault();
// update hrefs
updateHrefDurations(link.dataset.duration, "short" === link.dataset.length);
// update button text
link.closest(".login-duration__header").querySelector(".dropdown-trigger").textContent = link.textContent;
});
});
// remove wallclock option if we're too far from 19:00
if (msToEndOfWork > maxLongDuration) {
const walltimeSelector = document.querySelector(".dropdown-time__link--walltime[data-length=standard]");
walltimeSelector.closest(".login-duration__header").querySelector(".dropdown-trigger").textContent = defaultLongDurationLink.textContent;
walltimeSelector.remove();
}
// remove admin control if there are no admin permissions available
if (document.querySelectorAll(".federation__link--short").length === 0) {
document.querySelector(".login-duration--admin").style.display = "none";
}
});
// local times
document.querySelectorAll(".local-date").forEach(function(el) {
const dateSpan = el,
datestamp = dateSpan.getAttribute("data-date"),
pad = function(n, width, char) {
char = char || '0';
n = String(n);
return n.length >= width ? n : new Array(width - n.length + 1).join(char) + n;
};
if (datestamp) {
const d = new Date(Date.parse(datestamp));
dateSpan.textContent = pad(d.getHours(), 2) + ":" + pad(d.getMinutes(), 2) + ":" + pad(d.getSeconds(), 2);
}
});
// adjust for windows OS
document.querySelectorAll(".textarea--code.aws-profile-id").forEach(function(el) {
if (navigator.userAgent.includes("Win")) { // TODO: test this change in Windows
const winCmd = el.value.replace(/\\\n/g, "^\n").replace(/^ /mg, "");
el.value = winCmd;
}
});
{
// disable old auto-logout cookie to tidy up
// this can be removed in the near future, when this cookie will have been cleared out of colleague's browsers
const COOKIE__AUTO_LOGOUT = "janus_auto_logout";
document.cookie = `${COOKIE__AUTO_LOGOUT}=; expires=Thu, 01 Jan 1970 12:00:00 UTC; path=/`;
}
setUpRegisterPasskeyButton('#register-passkey');
// setupAuthButtons('.auth-button');
const protectedLinks = document.querySelectorAll('.passkey-protected');
setUpProtectedLinks(protectedLinks);
});