NoteJS/public/settings.js
Fovway f9ba1796dc Добавлены функции для управления заметками и логирования действий пользователей
- Реализованы функции для закрепления и архивирования заметок, а также их восстановления.
- Добавлены новые индексы в базу данных для улучшения производительности запросов.
- Внедрено логирование действий пользователей, включая регистрацию, вход, создание, обновление и удаление заметок.
- Обновлены интерфейсы для поддержки новых функций, включая кнопки для архивирования и закрепления заметок.
- Оптимизированы стили и добавлены новые элементы управления для улучшения пользовательского опыта.
2025-10-24 08:05:40 +07:00

360 lines
12 KiB
JavaScript
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.

// Переменные для пагинации логов
let logsOffset = 0;
const logsLimit = 50;
let hasMoreLogs = true;
// Проверка аутентификации
async function checkAuthentication() {
try {
const response = await fetch("/api/auth/status");
if (!response.ok) {
localStorage.removeItem("isAuthenticated");
localStorage.removeItem("username");
window.location.href = "/";
return false;
}
const authData = await response.json();
if (!authData.authenticated) {
localStorage.removeItem("isAuthenticated");
localStorage.removeItem("username");
window.location.href = "/";
return false;
}
return true;
} catch (error) {
console.error("Ошибка проверки аутентификации:", error);
return false;
}
}
// Загрузка информации о пользователе для применения accent color
async function loadUserInfo() {
try {
const response = await fetch("/api/user");
if (response.ok) {
const user = await response.json();
const accentColor = user.accent_color || "#007bff";
if (
getComputedStyle(document.documentElement)
.getPropertyValue("--accent-color")
.trim() !== accentColor
) {
document.documentElement.style.setProperty("--accent-color", accentColor);
}
}
} catch (error) {
console.error("Ошибка загрузки информации о пользователе:", error);
}
}
// Переключение табов
function initTabs() {
const tabs = document.querySelectorAll(".settings-tab");
const contents = document.querySelectorAll(".tab-content");
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
const tabName = tab.dataset.tab;
// Убираем активный класс со всех табов и контентов
tabs.forEach((t) => t.classList.remove("active"));
contents.forEach((c) => c.classList.remove("active"));
// Добавляем активный класс к выбранному табу и контенту
tab.classList.add("active");
document.getElementById(`${tabName}-tab`).classList.add("active");
// Загружаем данные для таба
if (tabName === "archive") {
loadArchivedNotes();
} else if (tabName === "logs") {
loadLogs(true);
}
});
});
}
// Загрузка архивных заметок
async function loadArchivedNotes() {
const container = document.getElementById("archived-notes-container");
container.innerHTML = '<p style="text-align: center; color: #999;">Загрузка...</p>';
try {
const response = await fetch("/api/notes/archived");
if (!response.ok) {
throw new Error("Ошибка загрузки архивных заметок");
}
const notes = await response.json();
if (notes.length === 0) {
container.innerHTML =
'<p style="text-align: center; color: #999;">Архив пуст</p>';
return;
}
container.innerHTML = "";
notes.forEach((note) => {
const noteDiv = document.createElement("div");
noteDiv.className = "archived-note-item";
noteDiv.dataset.noteId = note.id;
// Форматируем дату
const created = new Date(note.created_at.replace(" ", "T") + "Z");
const dateStr = new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(created);
// Преобразуем markdown в HTML для предпросмотра
const htmlContent = marked.parse(note.content);
const preview = htmlContent.substring(0, 200) + (htmlContent.length > 200 ? "..." : "");
// Изображения
let imagesHtml = "";
if (note.images && note.images.length > 0) {
imagesHtml = `<div class="archived-note-images">${note.images.length} изображений</div>`;
}
noteDiv.innerHTML = `
<div class="archived-note-header">
<span class="archived-note-date">${dateStr}</span>
<div class="archived-note-actions">
<button class="btn-restore" data-id="${note.id}" title="Восстановить">
<span class="iconify" data-icon="mdi:restore"></span> Восстановить
</button>
<button class="btn-delete-permanent" data-id="${note.id}" title="Удалить навсегда">
<span class="iconify" data-icon="mdi:delete-forever"></span> Удалить
</button>
</div>
</div>
<div class="archived-note-content">${preview}</div>
${imagesHtml}
`;
container.appendChild(noteDiv);
});
// Добавляем обработчики событий
addArchivedNotesEventListeners();
} catch (error) {
console.error("Ошибка загрузки архивных заметок:", error);
container.innerHTML =
'<p style="text-align: center; color: #dc3545;">Ошибка загрузки архивных заметок</p>';
}
}
// Добавление обработчиков для архивных заметок
function addArchivedNotesEventListeners() {
// Восстановление
document.querySelectorAll(".btn-restore").forEach((btn) => {
btn.addEventListener("click", async (e) => {
const noteId = e.target.closest("button").dataset.id;
if (confirm("Восстановить эту заметку из архива?")) {
try {
const response = await fetch(`/api/notes/${noteId}/unarchive`, {
method: "PUT",
});
if (!response.ok) {
throw new Error("Ошибка восстановления заметки");
}
// Удаляем элемент из списка
document
.querySelector(`[data-note-id="${noteId}"]`)
?.remove();
// Проверяем, остались ли заметки
const container = document.getElementById("archived-notes-container");
if (container.children.length === 0) {
container.innerHTML =
'<p style="text-align: center; color: #999;">Архив пуст</p>';
}
alert("Заметка восстановлена!");
} catch (error) {
console.error("Ошибка:", error);
alert("Ошибка восстановления заметки");
}
}
});
});
// Окончательное удаление
document.querySelectorAll(".btn-delete-permanent").forEach((btn) => {
btn.addEventListener("click", async (e) => {
const noteId = e.target.closest("button").dataset.id;
if (
confirm(
"Удалить эту заметку НАВСЕГДА? Это действие нельзя отменить!"
)
) {
try {
const response = await fetch(`/api/notes/archived/${noteId}`, {
method: "DELETE",
});
if (!response.ok) {
throw new Error("Ошибка удаления заметки");
}
// Удаляем элемент из списка
document
.querySelector(`[data-note-id="${noteId}"]`)
?.remove();
// Проверяем, остались ли заметки
const container = document.getElementById("archived-notes-container");
if (container.children.length === 0) {
container.innerHTML =
'<p style="text-align: center; color: #999;">Архив пуст</p>';
}
alert("Заметка удалена окончательно");
} catch (error) {
console.error("Ошибка:", error);
alert("Ошибка удаления заметки");
}
}
});
});
}
// Загрузка логов
async function loadLogs(reset = false) {
if (reset) {
logsOffset = 0;
hasMoreLogs = true;
}
const tbody = document.getElementById("logsTableBody");
const loadMoreContainer = document.getElementById("logsLoadMore");
const filterValue = document.getElementById("logTypeFilter").value;
if (reset) {
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">Загрузка...</td></tr>';
}
try {
let url = `/api/logs?limit=${logsLimit}&offset=${logsOffset}`;
if (filterValue) {
url += `&action_type=${filterValue}`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error("Ошибка загрузки логов");
}
const logs = await response.json();
if (reset) {
tbody.innerHTML = "";
}
if (logs.length === 0 && logsOffset === 0) {
tbody.innerHTML =
'<tr><td colspan="4" style="text-align: center; color: #999;">Логов пока нет</td></tr>';
loadMoreContainer.style.display = "none";
return;
}
if (logs.length < logsLimit) {
hasMoreLogs = false;
}
logs.forEach((log) => {
const row = document.createElement("tr");
// Форматируем дату
const created = new Date(log.created_at.replace(" ", "T") + "Z");
const dateStr = new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(created);
// Форматируем тип действия
const actionTypes = {
login: "Вход",
logout: "Выход",
register: "Регистрация",
note_create: "Создание заметки",
note_update: "Редактирование",
note_delete: "Удаление",
note_pin: "Закрепление",
note_archive: "Архивирование",
note_unarchive: "Восстановление",
note_delete_permanent: "Окончательное удаление",
profile_update: "Обновление профиля",
};
const actionText = actionTypes[log.action_type] || log.action_type;
row.innerHTML = `
<td>${dateStr}</td>
<td><span class="log-action-badge log-action-${log.action_type}">${actionText}</span></td>
<td>${log.details || "-"}</td>
<td>${log.ip_address || "-"}</td>
`;
tbody.appendChild(row);
});
logsOffset += logs.length;
if (hasMoreLogs && logs.length > 0) {
loadMoreContainer.style.display = "block";
} else {
loadMoreContainer.style.display = "none";
}
} catch (error) {
console.error("Ошибка загрузки логов:", error);
if (reset) {
tbody.innerHTML =
'<tr><td colspan="4" style="text-align: center; color: #dc3545;">Ошибка загрузки логов</td></tr>';
}
}
}
// Инициализация при загрузке страницы
document.addEventListener("DOMContentLoaded", async function () {
// Проверяем аутентификацию
const isAuth = await checkAuthentication();
if (!isAuth) return;
// Загружаем информацию о пользователе
await loadUserInfo();
// Инициализируем табы
initTabs();
// Загружаем архивные заметки по умолчанию
loadArchivedNotes();
// Обработчик фильтра логов
document.getElementById("logTypeFilter").addEventListener("change", () => {
loadLogs(true);
});
// Обработчик кнопки обновления логов
document.getElementById("refreshLogs").addEventListener("click", () => {
loadLogs(true);
});
// Обработчик кнопки "Загрузить еще"
document.getElementById("loadMoreLogsBtn").addEventListener("click", () => {
loadLogs(false);
});
});