@@ -0,0 +1,404 @@
|
||||
import { initThemePicker, initHighContrastToggle, initThemeToggle, applyTheme, getTheme } from "/static/js/wesp-theme.js";
|
||||
import {
|
||||
DEFAULT_PASSWORD_MIN_LEN,
|
||||
validateCredentialChange,
|
||||
} from "/static/js/wesp-user-credentials.js";
|
||||
|
||||
export function createRecipesAuthSettingsController({ notyf }) {
|
||||
let settingsModalClickHandler = null;
|
||||
let settingsEscapeHandler = null;
|
||||
let syncDispenserNames = [];
|
||||
let passwordMinLen = DEFAULT_PASSWORD_MIN_LEN;
|
||||
|
||||
function escapeHtmlSyncSettings(value) {
|
||||
if (value == null) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = String(value);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
const modal = document.getElementById("settingsModal");
|
||||
if (!modal) return;
|
||||
|
||||
modal.style.display = "none";
|
||||
document.body.style.overflow = "";
|
||||
|
||||
const form = document.getElementById("settingsForm");
|
||||
if (form) form.reset();
|
||||
|
||||
const credForm = document.getElementById("credentialsForm");
|
||||
if (credForm) credForm.style.display = "none";
|
||||
const userCard = document.querySelector(".settings-user-card--clickable");
|
||||
if (userCard) userCard.classList.remove("open");
|
||||
|
||||
const syncForm = document.getElementById("syncSettingsForm");
|
||||
if (syncForm) syncForm.style.display = "none";
|
||||
const syncCard = document.querySelector(".settings-sync-card--clickable");
|
||||
if (syncCard) syncCard.classList.remove("open");
|
||||
|
||||
if (settingsModalClickHandler) {
|
||||
modal.removeEventListener("click", settingsModalClickHandler);
|
||||
settingsModalClickHandler = null;
|
||||
}
|
||||
if (settingsEscapeHandler) {
|
||||
document.removeEventListener("keydown", settingsEscapeHandler);
|
||||
settingsEscapeHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function openSettings(event) {
|
||||
if (event) event.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/get_current_credentials");
|
||||
const data = await response.json();
|
||||
if (data.status === "success") {
|
||||
const oldLogin = document.getElementById("oldLogin");
|
||||
const oldPassword = document.getElementById("oldPassword");
|
||||
if (oldLogin) oldLogin.value = data.login || "";
|
||||
if (oldPassword) oldPassword.value = "";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Ошибка загрузки текущих учетных данных:", error);
|
||||
}
|
||||
|
||||
const modal = document.getElementById("settingsModal");
|
||||
if (!modal) return;
|
||||
modal.style.display = "block";
|
||||
document.body.style.overflow = "hidden";
|
||||
initThemePicker();
|
||||
initHighContrastToggle();
|
||||
|
||||
if (settingsModalClickHandler) {
|
||||
modal.removeEventListener("click", settingsModalClickHandler);
|
||||
}
|
||||
if (settingsEscapeHandler) {
|
||||
document.removeEventListener("keydown", settingsEscapeHandler);
|
||||
}
|
||||
|
||||
settingsModalClickHandler = (e) => {
|
||||
if (e.target === modal) closeSettings();
|
||||
};
|
||||
settingsEscapeHandler = (e) => {
|
||||
if (e.key === "Escape" && modal.style.display === "block") closeSettings();
|
||||
};
|
||||
|
||||
modal.addEventListener("click", settingsModalClickHandler);
|
||||
document.addEventListener("keydown", settingsEscapeHandler);
|
||||
}
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const response = await fetch("/api/auth/check");
|
||||
const data = await response.json();
|
||||
if (data.status === "success" && data.authenticated) {
|
||||
const userInfo = document.getElementById("userInfo");
|
||||
const userLogin = document.getElementById("userLogin");
|
||||
const mobileUserInfo = document.getElementById("mobileUserInfo");
|
||||
const canOpenAdmin = Boolean(data.is_superuser);
|
||||
window.WESP_CAN_LAB = Boolean(data.can_lab);
|
||||
window.WespLabAccess?.apply?.(window.WESP_CAN_LAB);
|
||||
const rules = data.user_rules || {};
|
||||
passwordMinLen = Number(rules.password_min_len) || DEFAULT_PASSWORD_MIN_LEN;
|
||||
if (userLogin) userLogin.textContent = data.user_login;
|
||||
if (userInfo) {
|
||||
userInfo.style.cursor = canOpenAdmin ? "pointer" : "";
|
||||
userInfo.title = canOpenAdmin ? "Открыть админ-панель" : "";
|
||||
userInfo.onclick = canOpenAdmin
|
||||
? () => {
|
||||
window.location.href = "/admin";
|
||||
}
|
||||
: null;
|
||||
}
|
||||
if (mobileUserInfo) {
|
||||
mobileUserInfo.innerHTML = `<i class="fas fa-user"></i> ${data.user_login}`;
|
||||
mobileUserInfo.style.cursor = canOpenAdmin ? "pointer" : "";
|
||||
mobileUserInfo.title = canOpenAdmin ? "Открыть админ-панель" : "";
|
||||
mobileUserInfo.onclick = canOpenAdmin
|
||||
? () => {
|
||||
window.location.href = "/admin";
|
||||
}
|
||||
: null;
|
||||
}
|
||||
} else {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Ошибка проверки авторизации:", error);
|
||||
window.location.href = "/login";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch("/api/auth/logout", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.status === "success") {
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
notyf.error("Ошибка при выходе из системы");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Ошибка при выходе:", error);
|
||||
notyf.error("Ошибка при выходе из системы");
|
||||
}
|
||||
}
|
||||
|
||||
function setupMobileMenu() {
|
||||
const hamburgerMenu = document.getElementById("hamburgerMenu");
|
||||
const mobileMenuOverlay = document.getElementById("mobileMenuOverlay");
|
||||
const closeMenu = document.getElementById("closeMenu");
|
||||
const mobileUserInfo = document.getElementById("mobileUserInfo");
|
||||
if (!hamburgerMenu || !mobileMenuOverlay || !closeMenu) return;
|
||||
|
||||
hamburgerMenu.addEventListener("click", () => {
|
||||
mobileMenuOverlay.classList.add("active");
|
||||
hamburgerMenu.classList.add("active");
|
||||
document.body.style.overflow = "hidden";
|
||||
const userInfo = document.getElementById("userInfo");
|
||||
if (userInfo && mobileUserInfo) {
|
||||
mobileUserInfo.textContent = userInfo.textContent || "";
|
||||
}
|
||||
});
|
||||
|
||||
const closeMobileMenu = () => {
|
||||
mobileMenuOverlay.classList.remove("active");
|
||||
hamburgerMenu.classList.remove("active");
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
|
||||
closeMenu.addEventListener("click", closeMobileMenu);
|
||||
mobileMenuOverlay.addEventListener("click", (e) => {
|
||||
if (e.target === mobileMenuOverlay) closeMobileMenu();
|
||||
});
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && mobileMenuOverlay.classList.contains("active")) {
|
||||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
|
||||
const mobileMenuItems = document.querySelectorAll(".mobile-menu-item");
|
||||
mobileMenuItems.forEach((item) => {
|
||||
item.addEventListener("click", () => {
|
||||
setTimeout(closeMobileMenu, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSyncClientsSettings() {
|
||||
const listEl = document.getElementById("syncClientsListSettings");
|
||||
const emptyEl = document.getElementById("syncClientsEmptySettings");
|
||||
if (!listEl) return;
|
||||
|
||||
listEl.innerHTML = "";
|
||||
if (emptyEl) emptyEl.style.display = "none";
|
||||
|
||||
try {
|
||||
const [clientsRes, namesRes] = await Promise.all([
|
||||
fetch("/api/sync/clients"),
|
||||
fetch("/api/feed_dispensers/names"),
|
||||
]);
|
||||
const clients = clientsRes.ok ? await clientsRes.json() : [];
|
||||
syncDispenserNames = namesRes.ok ? await namesRes.json() : [];
|
||||
|
||||
if (!clients || clients.length === 0) {
|
||||
if (emptyEl) {
|
||||
emptyEl.style.display = "block";
|
||||
emptyEl.textContent = "Нет подключенных клиентов";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
clients.forEach((client) => {
|
||||
const displayLabel = client.display_name || client.client_name || client.node_id;
|
||||
const item = document.createElement("div");
|
||||
item.className = "settings-sync-client-item";
|
||||
item.dataset.nodeId = client.node_id;
|
||||
item.dataset.displayName = displayLabel || "";
|
||||
item.innerHTML =
|
||||
`<span class="settings-sync-client-name">${escapeHtmlSyncSettings(displayLabel)}</span>` +
|
||||
'<div class="settings-sync-client-actions">' +
|
||||
'<button type="button" class="btn btn-sm btn-edit btn-outline-primary" data-action="sync-edit-client"><i class="fas fa-edit"></i> Изменить</button> ' +
|
||||
'<button type="button" class="btn btn-sm btn-delete btn-outline-danger" data-action="sync-delete-client"><i class="fas fa-trash"></i> Удалить</button>' +
|
||||
"</div>";
|
||||
listEl.appendChild(item);
|
||||
});
|
||||
} catch (error) {
|
||||
if (emptyEl) {
|
||||
emptyEl.style.display = "block";
|
||||
emptyEl.textContent = "Не удалось загрузить список клиентов";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function editSyncClientSettings(btn) {
|
||||
const item = btn && btn.closest ? btn.closest(".settings-sync-client-item") : null;
|
||||
const nodeId = item ? item.dataset.nodeId : null;
|
||||
const currentName = item ? item.dataset.displayName || "" : "";
|
||||
if (!item || !nodeId) return;
|
||||
|
||||
let selectHtml = '<option value="">— не задано —</option>';
|
||||
syncDispenserNames.forEach((name) => {
|
||||
const escaped = escapeHtmlSyncSettings(name);
|
||||
selectHtml += `<option value="${escaped}">${escaped}</option>`;
|
||||
});
|
||||
|
||||
item.innerHTML =
|
||||
'<div class="settings-sync-edit-row w-100">' +
|
||||
'<label class="mb-0 me-2">Имя клиента:</label>' +
|
||||
`<select class="form-control form-control-sm d-inline-block" id="syncEditSelect_${nodeId}">${selectHtml}</select> ` +
|
||||
`<button type="button" class="btn btn-sm btn-primary" data-action="sync-save-client" data-node-id="${String(nodeId).replace(/"/g, """)}"><i class="fas fa-save"></i> Сохранить</button> ` +
|
||||
'<button type="button" class="btn btn-sm btn-secondary" data-action="sync-cancel-edit">Отмена</button>' +
|
||||
"</div>";
|
||||
|
||||
const sel = document.getElementById(`syncEditSelect_${nodeId}`);
|
||||
if (sel && currentName) sel.value = currentName;
|
||||
}
|
||||
|
||||
function saveSyncClientDisplayName(nodeId) {
|
||||
const sel = document.getElementById(`syncEditSelect_${nodeId}`);
|
||||
const displayName = sel ? (sel.value || "").trim() : "";
|
||||
fetch(`/api/sync/clients/${encodeURIComponent(nodeId)}/display_name`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ display_name: displayName || null }),
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
notyf.error(data.error);
|
||||
return;
|
||||
}
|
||||
notyf.success(displayName ? "Имя сохранено" : "Имя сброшено");
|
||||
loadSyncClientsSettings();
|
||||
})
|
||||
.catch(() => notyf.error("Не удалось сохранить настройки клиента"));
|
||||
}
|
||||
|
||||
async function deleteSyncClientSettings(btn) {
|
||||
const item = btn && btn.closest ? btn.closest(".settings-sync-client-item") : null;
|
||||
const nodeId = item ? item.dataset.nodeId : null;
|
||||
if (!nodeId) return;
|
||||
const ok = globalThis.WespKioskDialog
|
||||
? await globalThis.WespKioskDialog.confirm("Удалить этого клиента из синхронизации?")
|
||||
: globalThis.confirm("Удалить этого клиента из синхронизации?");
|
||||
if (!ok) return;
|
||||
|
||||
fetch(`/api/sync/clients/${encodeURIComponent(nodeId)}`, { method: "DELETE" })
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
notyf.error(data.error);
|
||||
return;
|
||||
}
|
||||
notyf.success(data.message || "Клиент удалён");
|
||||
loadSyncClientsSettings();
|
||||
})
|
||||
.catch(() => notyf.error("Не удалось сохранить настройки клиента"));
|
||||
}
|
||||
|
||||
function toggleCredentialsForm() {
|
||||
const credForm = document.getElementById("credentialsForm");
|
||||
const userCard = document.querySelector(".settings-user-card--clickable");
|
||||
if (!credForm) return;
|
||||
|
||||
const isOpen = credForm.style.display !== "none";
|
||||
credForm.style.display = isOpen ? "none" : "block";
|
||||
if (userCard) userCard.classList.toggle("open", !isOpen);
|
||||
}
|
||||
|
||||
function toggleSyncForm() {
|
||||
const syncForm = document.getElementById("syncSettingsForm");
|
||||
const syncCard = document.querySelector(".settings-sync-card--clickable");
|
||||
if (!syncForm) return;
|
||||
|
||||
const isOpen = syncForm.style.display !== "none";
|
||||
syncForm.style.display = isOpen ? "none" : "block";
|
||||
if (syncCard) syncCard.classList.toggle("open", !isOpen);
|
||||
if (!isOpen) loadSyncClientsSettings();
|
||||
}
|
||||
|
||||
async function changeCredentials() {
|
||||
const oldLogin = (document.getElementById("oldLogin")?.value || "").trim();
|
||||
const oldPassword = document.getElementById("oldPassword")?.value || "";
|
||||
const newLogin = (document.getElementById("newLogin")?.value || "").trim();
|
||||
const newPassword = document.getElementById("newPassword")?.value || "";
|
||||
const confirmPassword = document.getElementById("confirmPassword")?.value || "";
|
||||
|
||||
const wantsCredChange = !!(newLogin || newPassword || confirmPassword.trim());
|
||||
if (!wantsCredChange) {
|
||||
notyf.success("Настройки сохранены");
|
||||
closeSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
const validationError = validateCredentialChange({
|
||||
oldLogin,
|
||||
oldPassword,
|
||||
newLogin,
|
||||
newPassword,
|
||||
confirmPassword,
|
||||
passwordMinLen,
|
||||
});
|
||||
if (validationError) {
|
||||
notyf.error(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/change_credentials", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
old_login: oldLogin,
|
||||
old_password: oldPassword,
|
||||
new_login: newLogin,
|
||||
new_password: newPassword,
|
||||
confirm_password: confirmPassword,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.status === "success") {
|
||||
notyf.success(data.message);
|
||||
closeSettings();
|
||||
const userLogin = document.getElementById("userLogin");
|
||||
const mobileUserInfo = document.getElementById("mobileUserInfo");
|
||||
if (userLogin) userLogin.textContent = newLogin;
|
||||
if (mobileUserInfo) {
|
||||
mobileUserInfo.innerHTML = `<i class="fas fa-user"></i> ${newLogin}`;
|
||||
}
|
||||
} else {
|
||||
notyf.error(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Ошибка при изменении учетных данных:", error);
|
||||
notyf.error("Ошибка при изменении учетных данных");
|
||||
}
|
||||
}
|
||||
|
||||
applyTheme(getTheme());
|
||||
|
||||
return {
|
||||
checkAuth,
|
||||
logout,
|
||||
setupMobileMenu,
|
||||
openSettings,
|
||||
closeSettings,
|
||||
toggleSyncForm,
|
||||
loadSyncClientsSettings,
|
||||
editSyncClientSettings,
|
||||
saveSyncClientDisplayName,
|
||||
deleteSyncClientSettings,
|
||||
toggleCredentialsForm,
|
||||
changeCredentials,
|
||||
initThemePicker,
|
||||
initHighContrastToggle,
|
||||
initThemeToggle,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user