poker/public/auth.js

547 lines
18 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.

/**
* =============================================================================
* Texas Hold'em - Модуль авторизации (клиент)
* =============================================================================
*/
// Текущий пользователь
let currentUser = null;
let authToken = null;
let isGuest = false;
// Пагинация логов
let currentLogPage = 1;
const logsPerPage = 20;
// =============================================================================
// АВТОРИЗАЦИЯ
// =============================================================================
/**
* Инициализация авторизации
*/
async function initAuth() {
// Проверяем сохранённый токен
const savedToken = localStorage.getItem('authToken');
if (savedToken) {
try {
const response = await fetch('/api/auth/me', {
headers: { 'Authorization': `Bearer ${savedToken}` }
});
if (response.ok) {
const data = await response.json();
currentUser = data.user;
authToken = savedToken;
onAuthSuccess();
return;
}
} catch (error) {
console.error('Ошибка проверки токена:', error);
}
// Токен недействителен
localStorage.removeItem('authToken');
}
// Показываем экран авторизации
showScreen('auth-screen');
}
/**
* Переключить вкладку авторизации
*/
function switchAuthTab(tab) {
const tabs = document.querySelectorAll('#auth-tabs .btn-tab');
tabs.forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.getElementById('login-form').style.display = tab === 'login' ? 'block' : 'none';
document.getElementById('register-form').style.display = tab === 'register' ? 'block' : 'none';
}
/**
* Обработчик входа
*/
async function handleLogin() {
const username = document.getElementById('login-username').value.trim();
const password = document.getElementById('login-password').value;
if (!username || !password) {
showNotification('Введите логин и пароль', 'error');
return;
}
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
currentUser = data.user;
authToken = data.token;
localStorage.setItem('authToken', authToken);
isGuest = false;
onAuthSuccess();
} else {
showNotification(data.error || 'Ошибка входа', 'error');
}
} catch (error) {
console.error('Ошибка входа:', error);
showNotification('Ошибка соединения с сервером', 'error');
}
}
/**
* Обработчик регистрации
*/
async function handleRegister() {
const username = document.getElementById('register-username').value.trim();
const password = document.getElementById('register-password').value;
const passwordConfirm = document.getElementById('register-password-confirm').value;
if (!username || !password) {
showNotification('Заполните все поля', 'error');
return;
}
if (password !== passwordConfirm) {
showNotification('Пароли не совпадают', 'error');
return;
}
if (username.length < 3) {
showNotification('Логин должен быть минимум 3 символа', 'error');
return;
}
if (password.length < 6) {
showNotification('Пароль должен быть минимум 6 символов', 'error');
return;
}
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
currentUser = data.user;
authToken = data.token;
localStorage.setItem('authToken', authToken);
isGuest = false;
showNotification('Регистрация успешна!', 'success');
onAuthSuccess();
} else {
showNotification(data.error || 'Ошибка регистрации', 'error');
}
} catch (error) {
console.error('Ошибка регистрации:', error);
showNotification('Ошибка соединения с сервером', 'error');
}
}
/**
* Играть как гость
*/
function playAsGuest() {
currentUser = {
id: 'guest_' + Date.now(),
username: ость_' + Math.floor(Math.random() * 10000),
role: 'guest'
};
isGuest = true;
authToken = null;
onAuthSuccess();
}
/**
* Выход
*/
function handleLogout() {
currentUser = null;
authToken = null;
isGuest = false;
localStorage.removeItem('authToken');
showScreen('auth-screen');
showNotification('Вы вышли из системы', 'info');
}
/**
* После успешной авторизации
*/
function onAuthSuccess() {
showScreen('main-menu');
updateUserDisplay();
loadLLMStatus();
// Загружаем настройки с сервера если не гость
if (!isGuest && authToken) {
loadUserSettingsFromServer();
}
}
/**
* Обновить отображение пользователя
*/
function updateUserDisplay() {
const nameEl = document.getElementById('user-display-name');
const badgeEl = document.getElementById('user-role-badge');
const adminBtn = document.getElementById('admin-btn');
const serverUrlGroup = document.getElementById('server-url-group');
if (currentUser) {
nameEl.textContent = currentUser.username;
if (currentUser.role === 'admin') {
badgeEl.style.display = 'inline';
badgeEl.textContent = 'ADMIN';
if (adminBtn) adminBtn.style.display = 'block';
if (serverUrlGroup) serverUrlGroup.style.display = 'block';
} else {
badgeEl.style.display = 'none';
if (adminBtn) adminBtn.style.display = 'none';
if (serverUrlGroup) serverUrlGroup.style.display = 'none';
}
// Обновляем поля имени в формах
document.getElementById('sp-player-name').value = currentUser.username;
document.getElementById('mp-player-name').value = currentUser.username;
}
}
// =============================================================================
// НАСТРОЙКИ С СЕРВЕРА
// =============================================================================
/**
* Загрузить пользовательские настройки с сервера
*/
async function loadUserSettingsFromServer() {
if (!authToken) return;
try {
const response = await fetch('/api/settings/user', {
headers: { 'Authorization': `Bearer ${authToken}` }
});
if (response.ok) {
const data = await response.json();
applyServerSettings(data.settings);
}
} catch (error) {
console.error('Ошибка загрузки настроек:', error);
}
}
/**
* Сохранить настройки на сервер
*/
async function saveUserSettingsToServer(settings) {
if (!authToken) return;
try {
await fetch('/api/settings/user', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(settings)
});
} catch (error) {
console.error('Ошибка сохранения настроек:', error);
}
}
/**
* Применить настройки с сервера
*/
function applyServerSettings(serverSettings) {
settings = {
...settings,
sound: serverSettings.sound,
animations: serverSettings.animations,
showHandStrength: serverSettings.showHandStrength,
autofold: serverSettings.autofold,
cardBackStyle: serverSettings.cardBackStyle,
cardBackCustom: serverSettings.cardBackCustom
};
// Применяем к UI
document.getElementById('setting-sound').checked = settings.sound !== false;
document.getElementById('setting-animations').checked = settings.animations !== false;
document.getElementById('setting-hand-strength').checked = settings.showHandStrength !== false;
document.getElementById('setting-autofold').checked = settings.autofold !== false;
soundEnabled = settings.sound !== false;
// Применяем рубашку карт
if (serverSettings.cardBackCustom) {
localStorage.setItem('customCardBack', serverSettings.cardBackCustom);
}
applyCardBackStyle(serverSettings.cardBackStyle || 'default');
}
/**
* Загрузить статус LLM
*/
async function loadLLMStatus() {
try {
const response = await fetch('/api/settings/public');
const data = await response.json();
const statusBox = document.getElementById('llm-status-box');
const statusIcon = document.getElementById('llm-status-icon');
const statusText = document.getElementById('llm-status-text');
if (statusBox) {
if (data.llmEnabled) {
statusBox.classList.add('enabled');
statusBox.classList.remove('disabled');
statusIcon.textContent = '✅';
statusText.textContent = `LLM чат включён (${data.llmProvider})`;
// Сохраняем настройки LLM глобально
window.llmSettings = {
enabled: true,
provider: data.llmProvider,
apiUrl: data.llmApiUrl,
model: data.llmModel
};
} else {
statusBox.classList.add('disabled');
statusBox.classList.remove('enabled');
statusIcon.textContent = '❌';
statusText.textContent = 'LLM чат отключён администратором';
window.llmSettings = { enabled: false };
}
}
} catch (error) {
console.error('Ошибка загрузки статуса LLM:', error);
}
}
// =============================================================================
// АДМИН-ПАНЕЛЬ
// =============================================================================
/**
* Переключить вкладку админки
*/
function switchAdminTab(tab) {
const tabs = document.querySelectorAll('.admin-tabs .btn-tab');
tabs.forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.querySelectorAll('.admin-tab-content').forEach(el => {
el.style.display = 'none';
});
document.getElementById(`admin-tab-${tab}`).style.display = 'block';
// Загружаем данные вкладки
if (tab === 'logs') {
loadLogStats();
loadLogs(1);
} else if (tab === 'settings') {
loadAdminSettings();
}
}
/**
* Загрузить админские настройки
*/
async function loadAdminSettings() {
if (!authToken || currentUser?.role !== 'admin') return;
try {
const response = await fetch('/api/settings/admin', {
headers: { 'Authorization': `Bearer ${authToken}` }
});
if (response.ok) {
const data = await response.json();
const s = data.settings;
document.getElementById('admin-llm-enabled').checked = s.llmEnabled;
document.getElementById('admin-llm-provider').value = s.llmProvider || 'ollama';
document.getElementById('admin-llm-url').value = s.llmApiUrl || 'http://localhost:11434';
document.getElementById('admin-llm-model').value = s.llmModel || 'llama3.2';
document.getElementById('admin-llm-apikey').value = s.llmApiKey || '';
}
} catch (error) {
console.error('Ошибка загрузки админ настроек:', error);
}
}
/**
* Сохранить админские настройки
*/
async function saveAdminSettings() {
if (!authToken || currentUser?.role !== 'admin') return;
const settings = {
llmEnabled: document.getElementById('admin-llm-enabled').checked,
llmProvider: document.getElementById('admin-llm-provider').value,
llmApiUrl: document.getElementById('admin-llm-url').value,
llmModel: document.getElementById('admin-llm-model').value,
llmApiKey: document.getElementById('admin-llm-apikey').value
};
try {
const response = await fetch('/api/settings/admin', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(settings)
});
if (response.ok) {
showNotification('Настройки сохранены', 'success');
}
} catch (error) {
showNotification('Ошибка сохранения', 'error');
}
}
/**
* Тестировать LLM из админки
*/
async function testAdminLLM() {
// Сохраняем настройки сначала
await saveAdminSettings();
showNotification('Тестирование LLM...', 'info');
// Здесь можно добавить реальный тест через сервер
setTimeout(() => {
showNotification('LLM тест выполнен (проверьте логи сервера)', 'success');
}, 1000);
}
/**
* Загрузить статистику логов
*/
async function loadLogStats() {
if (!authToken || currentUser?.role !== 'admin') return;
try {
const response = await fetch('/api/logs/stats', {
headers: { 'Authorization': `Bearer ${authToken}` }
});
if (response.ok) {
const data = await response.json();
document.getElementById('stat-total').textContent = data.stats.total;
document.getElementById('stat-today').textContent = data.stats.today;
}
} catch (error) {
console.error('Ошибка загрузки статистики:', error);
}
}
/**
* Загрузить логи
*/
async function loadLogs(page = 1) {
if (!authToken || currentUser?.role !== 'admin') return;
currentLogPage = Math.max(1, page);
const offset = (currentLogPage - 1) * logsPerPage;
const actionType = document.getElementById('log-filter-type').value;
try {
let url = `/api/logs?limit=${logsPerPage}&offset=${offset}`;
if (actionType) url += `&actionType=${actionType}`;
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${authToken}` }
});
if (response.ok) {
const data = await response.json();
renderLogs(data.logs);
// Обновляем пагинацию
document.getElementById('log-page-info').textContent = `Страница ${currentLogPage}`;
document.getElementById('log-prev-btn').disabled = currentLogPage === 1;
document.getElementById('log-next-btn').disabled = data.logs.length < logsPerPage;
}
} catch (error) {
console.error('Ошибка загрузки логов:', error);
}
}
/**
* Отрисовать логи
*/
function renderLogs(logs) {
const tbody = document.getElementById('log-table-body');
tbody.innerHTML = '';
if (logs.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; color: var(--text-muted);">Логи не найдены</td></tr>';
return;
}
logs.forEach(log => {
const tr = document.createElement('tr');
const time = new Date(log.timestamp).toLocaleString('ru-RU');
const details = typeof log.actionData === 'object'
? JSON.stringify(log.actionData).substring(0, 50) + '...'
: log.actionData;
tr.innerHTML = `
<td>${time}</td>
<td>${log.username || 'Гость'}</td>
<td><span class="log-action-badge ${log.actionType}">${log.actionType}</span></td>
<td title="${JSON.stringify(log.actionData)}">${details}</td>
`;
tbody.appendChild(tr);
});
}
// =============================================================================
// ОЧИСТКА ЧАТА
// =============================================================================
/**
* Очистить чат при смене режима игры
*/
function clearGameChat() {
const container = document.getElementById('game-chat-messages');
if (container) {
container.innerHTML = '';
}
}
/**
* Очистить чат лобби
*/
function clearLobbyChat() {
const container = document.getElementById('lobby-chat-messages');
if (container) {
container.innerHTML = '';
}
}
// Экспортируем для глобального использования
window.currentUser = currentUser;
window.authToken = authToken;
window.isGuest = isGuest;