налажена логика добавления промптов через диалог с ИИ, отлажена работа с задачами, настроена авторизация в веб интерфейсе.

This commit is contained in:
2026-08-09 13:33:55 +03:00
parent 8a3f3c749f
commit 2fa151345f
9 changed files with 842 additions and 280 deletions
+252 -60
View File
@@ -1,79 +1,68 @@
let authMode = 'login'; // 'login' или 'register'
function showAuthModal() {
document.getElementById('auth-modal').classList.remove('hidden');
const el = document.getElementById("auth-modal");
if (el) el.classList.remove("hidden");
}
function hideAuthModal() {
document.getElementById('auth-modal').classList.add('hidden');
}
function setAuthMode(mode) {
authMode = mode;
const titleEl = document.getElementById('auth-title');
const submitBtnText = document.getElementById('auth-submit-text');
const toggleBtn = document.getElementById('auth-toggle-btn');
const errorEl = document.getElementById('auth-error');
errorEl.classList.add('hidden');
if (mode === 'register') {
titleEl.innerText = "Регистрация нового пользователя";
submitBtnText.innerText = "Зарегистрироваться";
toggleBtn.innerText = "Уже есть аккаунт? Войти";
} else {
titleEl.innerText = "Авторизация в системе";
submitBtnText.innerText = "Войти в систему";
toggleBtn.innerText = "Создать новый аккаунт";
}
}
function toggleAuthMode() {
setAuthMode(authMode === 'login' ? 'register' : 'login');
const el = document.getElementById("auth-modal");
if (el) el.classList.add("hidden");
}
async function handleLogin(e) {
e.preventDefault();
const usernameInput = document.getElementById('auth-username-input');
const passwordInput = document.getElementById('auth-password-input');
const errorEl = document.getElementById('auth-error');
if (e && e.preventDefault) e.preventDefault();
const usernameInput = document.getElementById("auth-username-input");
const passwordInput = document.getElementById("auth-password-input");
const errorEl = document.getElementById("auth-error");
if (!usernameInput || !passwordInput) return;
const username = usernameInput.value.trim();
const password = passwordInput.value;
if (!username || !password) return;
errorEl.classList.add('hidden');
const endpoint = authMode === 'register' ? '/api/v1/auth/register' : '/api/v1/auth/login';
if (errorEl) errorEl.classList.add("hidden");
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
const res = await fetch("/api/v1/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (res.status === 200) {
// Используем прямой строковый ключ, чтобы избежать ошибки ReferenceError
API_TOKEN = data.token;
CURRENT_USERNAME = data.username;
IS_ADMIN = data.is_admin;
IS_GUEST = false;
localStorage.setItem(AUTH_TOKEN_KEY, data.token);
localStorage.setItem('scud_username', data.username);
localStorage.removeItem('scud_is_guest');
localStorage.setItem("scud_api_auth_token", data.token);
localStorage.setItem("scud_username", data.username);
localStorage.setItem("scud_is_admin", data.is_admin ? "true" : "false");
localStorage.removeItem("scud_is_guest");
hideAuthModal();
updateUIState();
loadTasks();
if (typeof loadTasks === 'function') {
loadTasks();
}
} else {
errorEl.innerText = data.detail || "Ошибка авторизации";
errorEl.classList.remove('hidden');
if (errorEl) {
errorEl.innerText = data.detail || "Ошибка авторизации";
errorEl.classList.remove("hidden");
}
}
} catch (err) {
errorEl.innerText = "Ошибка соединения с сервером";
errorEl.classList.remove('hidden');
console.error("[Auth Error]", err);
if (errorEl) {
errorEl.innerText = "Ошибка соединения с сервером";
errorEl.classList.remove("hidden");
}
}
}
@@ -81,36 +70,239 @@ function enableGuestMode() {
IS_GUEST = true;
API_TOKEN = "";
CURRENT_USERNAME = "Гость";
localStorage.setItem('scud_is_guest', 'true');
IS_ADMIN = false;
localStorage.setItem("scud_is_guest", "true");
hideAuthModal();
updateUIState();
}
function logout() {
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem('scud_username');
localStorage.removeItem('scud_is_guest');
localStorage.removeItem("scud_api_auth_token");
localStorage.removeItem("scud_username");
localStorage.removeItem("scud_is_admin");
localStorage.removeItem("scud_is_guest");
API_TOKEN = "";
CURRENT_USERNAME = "";
IS_ADMIN = false;
IS_GUEST = false;
showAuthModal();
}
function updateUIState() {
const tasksBtn = document.getElementById('tasks-drawer-btn');
const guestBadge = document.getElementById('guest-badge');
const usernameBadge = document.getElementById('username-badge');
const tasksBtn = document.getElementById("tasks-drawer-btn");
const adminBtn = document.getElementById("admin-users-btn");
const changePwdBtn = document.getElementById("change-pwd-btn");
const guestBadge = document.getElementById("guest-badge");
const usernameBadge = document.getElementById("username-badge");
if (IS_GUEST) {
if (tasksBtn) tasksBtn.classList.add('hidden');
if (guestBadge) guestBadge.classList.remove('hidden');
if (usernameBadge) usernameBadge.classList.add('hidden');
if (typeof IS_GUEST !== 'undefined' && IS_GUEST) {
if (tasksBtn) tasksBtn.classList.add("hidden");
if (adminBtn) adminBtn.classList.add("hidden");
if (changePwdBtn) changePwdBtn.classList.add("hidden");
if (guestBadge) guestBadge.classList.remove("hidden");
if (usernameBadge) usernameBadge.classList.add("hidden");
} else {
if (tasksBtn) tasksBtn.classList.remove('hidden');
if (guestBadge) guestBadge.classList.add('hidden');
if (tasksBtn) tasksBtn.classList.remove("hidden");
if (changePwdBtn) changePwdBtn.classList.remove("hidden");
if (guestBadge) guestBadge.classList.add("hidden");
if (usernameBadge) {
usernameBadge.innerText = CURRENT_USERNAME || 'User';
usernameBadge.classList.remove('hidden');
usernameBadge.innerText = (typeof CURRENT_USERNAME !== 'undefined' && CURRENT_USERNAME) ? CURRENT_USERNAME : "User";
usernameBadge.classList.remove("hidden");
}
if (adminBtn) {
const isAdminUser = (typeof IS_ADMIN !== 'undefined' && IS_ADMIN) || (typeof CURRENT_USERNAME !== 'undefined' && CURRENT_USERNAME === "puh");
if (isAdminUser) {
adminBtn.classList.remove("hidden");
} else {
adminBtn.classList.add("hidden");
}
}
}
}
function openChangePasswordModal() {
const el = document.getElementById("change-pwd-modal");
if (el) el.classList.remove("hidden");
}
function closeChangePasswordModal() {
const el = document.getElementById("change-pwd-modal");
if (el) el.classList.add("hidden");
const err = document.getElementById("pwd-error");
const succ = document.getElementById("pwd-success");
if (err) err.classList.add("hidden");
if (succ) succ.classList.add("hidden");
document.getElementById("old-pwd-input").value = "";
document.getElementById("new-pwd-input").value = "";
const confirmInput = document.getElementById("confirm-pwd-input");
if (confirmInput) confirmInput.value = "";
}
async function handleChangePassword(e) {
if (e && e.preventDefault) e.preventDefault();
const old_password = document.getElementById("old-pwd-input").value;
const new_password = document.getElementById("new-pwd-input").value;
const confirmInput = document.getElementById("confirm-pwd-input");
const confirm_password = confirmInput ? confirmInput.value : new_password;
const errorEl = document.getElementById("pwd-error");
const successEl = document.getElementById("pwd-success");
if (errorEl) errorEl.classList.add("hidden");
if (successEl) successEl.classList.add("hidden");
if (new_password !== confirm_password) {
if (errorEl) {
errorEl.innerText = "Новые пароли не совпадают";
errorEl.classList.remove("hidden");
}
return;
}
try {
const token = typeof API_TOKEN !== 'undefined' ? API_TOKEN : localStorage.getItem("scud_api_auth_token");
const res = await fetch("/api/v1/auth/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: JSON.stringify({ old_password, new_password })
});
const data = await res.json();
if (res.status === 200) {
if (successEl) {
successEl.innerText = "Пароль успешно изменен!";
successEl.classList.remove("hidden");
}
setTimeout(closeChangePasswordModal, 1500);
} else {
if (errorEl) {
errorEl.innerText = data.detail || "Ошибка при смене пароля";
errorEl.classList.remove("hidden");
}
}
} catch (err) {
if (errorEl) {
errorEl.innerText = "Ошибка соединения с сервером";
errorEl.classList.remove("hidden");
}
}
}
function openAdminModal() {
const el = document.getElementById("admin-modal");
if (el) el.classList.remove("hidden");
loadUsersList();
}
function closeAdminModal() {
const el = document.getElementById("admin-modal");
if (el) el.classList.add("hidden");
}
async function loadUsersList() {
const listEl = document.getElementById("admin-users-list");
if (!listEl) return;
listEl.innerHTML = '<div class="text-xs text-slate-400 py-4 text-center">Загрузка пользователей...</div>';
try {
const token = typeof API_TOKEN !== 'undefined' ? API_TOKEN : localStorage.getItem("scud_api_auth_token");
const res = await fetch("/api/v1/admin/users", {
headers: { "Authorization": "Bearer " + token }
});
const users = await res.json();
if (res.status === 200) {
listEl.innerHTML = users.map(u => {
const adminTag = u.is_admin ? '<span class="ml-1.5 text-[9px] bg-indigo-100 text-indigo-700 px-1.5 py-0.5 rounded font-bold">ADMIN</span>' : '<span class="ml-1.5 text-[9px] bg-slate-100 text-slate-600 px-1.5 py-0.5 rounded">USER</span>';
const fullNameHtml = u.full_name ? `<div class="text-[11px] text-slate-500 font-normal">${u.full_name}</div>` : '';
const dateStr = u.created_at ? u.created_at.split(' ')[0] : '—';
const deleteBtn = u.username !== CURRENT_USERNAME ? `<button type="button" onclick="deleteUser(${u.id}, '${u.username}')" class="text-red-500 hover:text-red-700 p-1"><i class="fa-solid fa-trash-can"></i></button>` : '<span class="text-[10px] text-slate-400">Вы</span>';
return `
<div class="flex justify-between items-center bg-slate-50 border border-slate-200 p-2.5 rounded-xl text-xs">
<div>
<div class="flex items-center">
<span class="font-bold text-slate-800">${u.username}</span>
${adminTag}
</div>
${fullNameHtml}
<div class="text-[10px] text-slate-400 mt-0.5">Создан: ${dateStr}</div>
</div>
${deleteBtn}
</div>
`;
}).join("");
} else {
listEl.innerHTML = `<div class="text-xs text-red-500 py-2">${users.detail}</div>`;
}
} catch (err) {
listEl.innerHTML = '<div class="text-xs text-red-500 py-2">Ошибка загрузки пользователей</div>';
}
}
async function handleCreateUser(e) {
if (e && e.preventDefault) e.preventDefault();
const username = document.getElementById("new-user-name").value.trim();
const password = document.getElementById("new-user-pwd").value;
const fullNameInput = document.getElementById("new-user-fullname");
const full_name = fullNameInput ? fullNameInput.value.trim() : "";
const adminCheckbox = document.getElementById("new-user-is-admin");
const is_admin = adminCheckbox ? adminCheckbox.checked : false;
const msgEl = document.getElementById("admin-msg");
if (msgEl) msgEl.classList.add("hidden");
try {
const token = typeof API_TOKEN !== 'undefined' ? API_TOKEN : localStorage.getItem("scud_api_auth_token");
const res = await fetch("/api/v1/admin/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: JSON.stringify({ username, password, full_name, is_admin })
});
const data = await res.json();
if (res.status === 200) {
document.getElementById("new-user-name").value = "";
document.getElementById("new-user-pwd").value = "";
if (fullNameInput) fullNameInput.value = "";
if (adminCheckbox) adminCheckbox.checked = false;
loadUsersList();
} else {
if (msgEl) {
msgEl.innerText = data.detail || "Ошибка";
msgEl.classList.remove("hidden");
}
}
} catch (err) {
if (msgEl) {
msgEl.innerText = "Ошибка связи с сервером";
msgEl.classList.remove("hidden");
}
}
}
async function deleteUser(userId, username) {
if (!confirm("Удалить пользователя " + username + "?")) return;
try {
const token = typeof API_TOKEN !== 'undefined' ? API_TOKEN : localStorage.getItem("scud_api_auth_token");
await fetch("/api/v1/admin/users/" + userId, {
method: "DELETE",
headers: { "Authorization": "Bearer " + token }
});
loadUsersList();
} catch (err) {
alert("Ошибка при удалении");
}
}