NODEDC_PLATFORM/infra/authentik/custom-templates/base/header_js.html

287 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
<script data-id="authentik-config">
"use strict";
window.authentik = {
locale: "ru",
config: JSON.parse('{{ config_json|escapejs }}' || "{}"),
brand: JSON.parse('{{ brand_json|escapejs }}' || "{}"),
versionFamily: "{{ version_family }}",
versionSubdomain: "{{ version_subdomain }}",
build: "{{ build }}",
api: {
base: "{{ base_url }}",
relBase: "{{ base_url_rel }}",
},
};
window.addEventListener("DOMContentLoaded", function () {
{% for message in messages %}
window.dispatchEvent(
new CustomEvent("ak-message", {
bubbles: true,
composed: true,
detail: {
level: "{{ message.tags|escapejs }}",
message: "{{ message.message|escapejs }}",
},
}),
);
{% endfor %}
});
</script>
<script data-id="nodedc-auth-field-enhancements">
"use strict";
(function () {
const logoSvg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220.82 54.55" aria-hidden="true">
<path fill="currentColor" d="M52.8 23.61 46.92 33.76 41.05 23.61H52.8m18-10.39H23.06l23.86 41.33Z"></path>
<polygon fill="currentColor" points="31.28 33.13 18.11 10.34 75.73 10.34 62.59 33.13 74.28 33.13 93.22 0 0 0 19.61 33.13 31.28 33.13"></polygon>
<path fill="currentColor" d="M116.35 18.49V1h1.27l10.34 15V1h1.33v17.49H128l-10.34-15v15Zm24.08.15c-4.79 0-8.16-3.72-8.16-8.89S135.64.86 140.43.86s8.17 3.72 8.17 8.89-3.35 8.89-8.17 8.89Zm0-1.25c4 0 6.79-3.17 6.79-7.64s-2.77-7.64-6.79-7.64-6.77 3.17-6.77 7.64 2.78 7.64 6.77 7.64ZM151.6 18.49V1h5.1c5.54 0 8.79 3.42 8.79 8.74s-3.25 8.74-8.79 8.74Zm1.4-1.25h3.75c4.77 0 7.42-2.92 7.42-7.49s-2.65-7.49-7.42-7.49H153ZM168.49 1h10.77v1.26h-9.42v6.67h7.89v1.25h-7.89v7.06h9.74v1.25h-11.09Zm20.39 17.49V1H194c5.54 0 8.79 3.42 8.79 8.74s-3.25 8.74-8.79 8.74Zm1.35-1.25H194c4.77 0 7.41-2.92 7.41-7.49S198.75 2.26 194 2.26h-3.75Zm14.92-7.49c0-5.24 3.19-8.89 8.11-8.89a6.8 6.8 0 0 1 7.1 5.52h-1.43a5.54 5.54 0 0 0-5.74-4.27c-4.05 0-6.64 3.17-6.64 7.64s2.54 7.64 6.59 7.64a5.46 5.46 0 0 0 5.74-4.29h1.43c-.75 3.52-3.4 5.54-7.15 5.54-4.89 0-8.01-3.59-8.01-8.89Z"></path>
</svg>
`;
const clearIcon = `
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"></circle>
<path d="M9 9l6 6M15 9l-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
</svg>
`;
const authTranslations = new Map([
["Failed to authenticate.", "Не удалось выполнить вход."],
["Invalid credentials.", "Неверная почта или пароль."],
["Invalid password.", "Неверный пароль."],
["Incorrect password.", "Неверный пароль."],
["Authentication failed.", "Не удалось выполнить вход."],
["This field is required.", "Заполните это поле."],
["Please enter your password", "Введите пароль"],
["Please enter your password.", "Введите пароль."],
["Please enter a valid email address.", "Введите корректную электронную почту."],
["Caps Lock is enabled.", "Включён Caps Lock."],
["Forgot username or password?", "Забыли пароль?"],
["Forgot password?", "Забыли пароль?"],
["Remember me on this device", "Запомнить на этом устройстве"],
["Email", "Эл. почта"],
["Email or Username", "Эл. почта"],
["Password", "Пароль"],
["Log in", "Войти"],
["Continue", "Продолжить"],
["Show password", "Показать пароль"],
["Hide password", "Скрыть пароль"],
]);
function visitRoots(root, callback) {
callback(root);
root.querySelectorAll("*").forEach((element) => {
if (element.shadowRoot) {
visitRoots(element.shadowRoot, callback);
}
});
}
function ensureLogo() {
if (!document.body || document.querySelector("[data-nodedc-auth-logo='true']")) return;
document.body.classList.add("nodedc-auth-enhanced");
const logo = document.createElement("div");
logo.className = "nodedc-auth-logo";
logo.dataset.nodedcAuthLogo = "true";
logo.innerHTML = logoSvg;
document.body.appendChild(logo);
}
function translateTextValue(value) {
if (!value) return value;
const trimmed = value.trim();
const translated = authTranslations.get(trimmed);
if (!translated) return value;
return value.replace(trimmed, translated);
}
function translateAuthText(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
const parent = node.parentElement;
if (!parent || !node.nodeValue?.trim()) return NodeFilter.FILTER_REJECT;
if (["SCRIPT", "STYLE", "SVG", "PATH", "POLYGON"].includes(parent.tagName)) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
},
});
const textNodes = [];
while (walker.nextNode()) {
textNodes.push(walker.currentNode);
}
textNodes.forEach((node) => {
const translated = translateTextValue(node.nodeValue);
if (translated !== node.nodeValue) {
node.nodeValue = translated;
}
});
root.querySelectorAll("[aria-label], [title], [placeholder], input[value], button").forEach((element) => {
["aria-label", "title", "placeholder"].forEach((attribute) => {
const value = element.getAttribute(attribute);
const translated = translateTextValue(value);
if (translated !== value) element.setAttribute(attribute, translated);
});
});
}
function enhanceIdentifierField(root) {
const input = root.querySelector("#ak-identifier-input");
if (!input || input.dataset.nodedcClearBound === "true") return;
const fieldGroup = input.closest(".pf-c-form__group") || input.parentElement;
if (!fieldGroup) return;
input.dataset.nodedcClearBound = "true";
fieldGroup.style.position = "relative";
const button = document.createElement("button");
button.type = "button";
button.tabIndex = -1;
button.className = "nodedc-auth-clear-input";
button.dataset.nodedcClearInput = "true";
button.setAttribute("aria-label", "Очистить электронную почту");
button.innerHTML = clearIcon;
const update = () => {
button.dataset.visible = input.value ? "true" : "false";
};
button.addEventListener("click", (event) => {
event.preventDefault();
input.value = "";
input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
input.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
input.focus();
update();
});
input.addEventListener("input", update);
input.addEventListener("change", update);
fieldGroup.appendChild(button);
update();
}
function maskPasswordInput(input) {
if (!input || input.type !== "text") return;
input.type = "password";
const button = input.closest(".pf-c-input-group")?.querySelector(".pf-c-button.pf-m-control");
const icon = button?.querySelector("i");
button?.setAttribute("aria-label", "Показать пароль");
icon?.classList.remove("fa-eye-slash");
icon?.classList.add("fa-eye");
}
function maskVisiblePasswords(target) {
visitRoots(document, (root) => {
root.querySelectorAll("#ak-stage-identification-password, #ak-stage-password-input").forEach((input) => {
const inputGroup = input.closest(".pf-c-input-group");
if (inputGroup?.contains(target)) return;
maskPasswordInput(input);
});
});
}
function enhancePasswordFields(root) {
root.querySelectorAll("#ak-stage-identification-password, #ak-stage-password-input").forEach((input) => {
if (input.placeholder !== "Введите пароль") {
input.placeholder = "Введите пароль";
}
if (input.dataset.nodedcBlurBound === "true") return;
input.dataset.nodedcBlurBound = "true";
input.addEventListener("blur", () => {
window.setTimeout(() => {
const inputGroup = input.closest(".pf-c-input-group");
if (inputGroup?.contains(document.activeElement)) return;
maskPasswordInput(input);
}, 0);
});
});
}
function normalizePasswordFields(root) {
enhancePasswordFields(root);
}
function enhanceSubmitHandoff(root) {
root.querySelectorAll("form").forEach((form) => {
if (form.dataset.nodedcSubmitBound === "true") return;
form.dataset.nodedcSubmitBound = "true";
form.addEventListener("submit", () => {
document.body?.classList.add("nodedc-auth-submitting");
}, true);
});
}
function hasAuthError(root) {
const hasErrorElement = root.querySelector(
".pf-c-alert, .pf-m-error, .pf-c-helper-text__item.pf-m-error, [aria-invalid='true']"
);
if (hasErrorElement) return true;
const text = root.textContent || "";
return [
"Failed to authenticate.",
"Не удалось выполнить вход.",
"Invalid credentials.",
"Неверная почта или пароль.",
"Invalid password.",
"Неверный пароль.",
].some((message) => text.includes(message));
}
function restoreCardOnErrors(root) {
if (hasAuthError(root)) {
document.body?.classList.remove("nodedc-auth-submitting");
}
}
let scheduled = false;
function enhanceAuthFields() {
scheduled = false;
ensureLogo();
visitRoots(document, (root) => {
enhanceIdentifierField(root);
normalizePasswordFields(root);
enhanceSubmitHandoff(root);
translateAuthText(root);
restoreCardOnErrors(root);
});
}
function scheduleEnhancement() {
if (scheduled) return;
scheduled = true;
window.requestAnimationFrame(enhanceAuthFields);
}
window.addEventListener("DOMContentLoaded", scheduleEnhancement);
window.addEventListener("load", scheduleEnhancement);
document.addEventListener("pointerdown", (event) => {
const target = event.composedPath?.()[0] || event.target;
maskVisiblePasswords(target);
}, true);
window.addEventListener("blur", () => maskVisiblePasswords(null));
new MutationObserver(scheduleEnhancement).observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>