Files
play-life/play-life-backend/admin.html
2026-02-08 17:01:36 +03:00

395 lines
13 KiB
HTML
Raw Permalink 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.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Play Life Backend - Admin Panel</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.card h2 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.3em;
display: flex;
align-items: center;
gap: 10px;
}
.card textarea {
width: 100%;
min-height: 150px;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 5px;
font-size: 14px;
font-family: monospace;
resize: vertical;
margin-bottom: 15px;
}
.card textarea:focus {
outline: none;
border-color: #667eea;
}
.card button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 5px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
width: 100%;
}
.card button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.card button:active {
transform: translateY(0);
}
.card button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.result {
margin-top: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
border-left: 4px solid #667eea;
}
.result h3 {
color: #333;
margin-bottom: 10px;
font-size: 1.1em;
}
.result pre {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
font-size: 12px;
line-height: 1.5;
max-height: 400px;
overflow-y: auto;
}
.result.success {
border-left-color: #4caf50;
}
.result.error {
border-left-color: #f44336;
}
.result.loading {
border-left-color: #ff9800;
}
.status {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
margin-left: 10px;
}
.status.success {
background: #4caf50;
color: white;
}
.status.error {
background: #f44336;
color: white;
}
.status.loading {
background: #ff9800;
color: white;
}
.auth-error {
background: white;
padding: 30px;
border-radius: 10px;
text-align: center;
max-width: 500px;
margin: 50px auto;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.auth-error h2 {
color: #f44336;
margin-bottom: 15px;
}
.auth-error p {
color: #666;
margin-bottom: 20px;
}
.auth-error a {
display: inline-block;
padding: 10px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-decoration: none;
border-radius: 5px;
font-weight: 600;
}
.auth-error a:hover {
opacity: 0.9;
}
</style>
</head>
<body>
<div id="authErrorContainer" style="display: none;">
<div class="auth-error">
<h2>⚠️ Требуется авторизация</h2>
<p id="authErrorMessage">Для доступа к админ-панели необходимо войти в систему как администратор.</p>
<a href="/" target="_self">Перейти на главную страницу</a>
</div>
</div>
<div class="container" id="mainContainer">
<h1>🎯 Play Life Backend - Admin Panel</h1>
<div class="grid">
<!-- Weekly Goals Setup Card -->
<div class="card">
<h2>
🎯 Weekly Goals Setup
<span class="status" id="goalsStatus" style="display: none;"></span>
</h2>
<p style="margin-bottom: 15px; color: #666;">
Нажмите кнопку для установки целей на текущую неделю на основе медианы за последние 3 месяца (с отправкой в чат). Обычно срабатывает автоматически в начале недели.
</p>
<button onclick="setupWeeklyGoals()">Обновить цели</button>
<div id="goalsResult"></div>
</div>
<!-- Project score sample MV Card -->
<div class="card">
<h2>
📊 project_score_sample_mv
<span class="status" id="mvStatus" style="display: none;"></span>
</h2>
<p style="margin-bottom: 15px; color: #666;">
Обновить материализованное представление и показать данные текущего пользователя (по одному представителю на вариант баллов проекта).
</p>
<button onclick="refreshProjectScoreSampleMv()">Обновить project_score_sample_mv</button>
<div id="mvResult"></div>
</div>
</div>
</div>
<script>
// Получаем токен из localStorage
function getAuthToken() {
return localStorage.getItem('access_token');
}
// Проверяем авторизацию при загрузке страницы
function checkAuth() {
const token = getAuthToken();
if (!token) {
showAuthError('Токен авторизации не найден. Пожалуйста, войдите в систему.');
return false;
}
return true;
}
// Показываем сообщение об ошибке авторизации
function showAuthError(message) {
document.getElementById('authErrorContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
document.getElementById('authErrorMessage').textContent = message;
}
// Обрабатываем ошибки авторизации
function handleAuthError(response) {
if (response.status === 401) {
showAuthError('Сессия истекла. Пожалуйста, войдите в систему снова.');
return true;
} else if (response.status === 403) {
showAuthError('У вас нет прав доступа к админ-панели. Требуются права администратора.');
return true;
}
return false;
}
// Получаем заголовки с авторизацией
function getAuthHeaders() {
const token = getAuthToken();
const headers = {
'Content-Type': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
function getApiUrl() {
// Автоматически определяем URL текущего хоста
// Админка обслуживается тем же бекендом, поэтому используем текущий origin
return window.location.origin;
}
// Проверяем авторизацию при загрузке страницы
if (!checkAuth()) {
// Страница уже скрыта в checkAuth
}
function showStatus(elementId, status, text) {
const statusEl = document.getElementById(elementId);
statusEl.textContent = text;
statusEl.className = `status ${status}`;
statusEl.style.display = 'inline-block';
}
function hideStatus(elementId) {
document.getElementById(elementId).style.display = 'none';
}
function showResult(elementId, data, isError = false, isLoading = false) {
const resultEl = document.getElementById(elementId);
resultEl.innerHTML = '';
if (isLoading) {
resultEl.innerHTML = '<div class="result loading"><h3>⏳ Загрузка...</h3></div>';
return;
}
const div = document.createElement('div');
div.className = `result ${isError ? 'error' : 'success'}`;
const h3 = document.createElement('h3');
h3.textContent = isError ? '❌ Ошибка' : '✅ Успешно';
div.appendChild(h3);
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(data, null, 2);
div.appendChild(pre);
resultEl.appendChild(div);
}
async function setupWeeklyGoals() {
showStatus('goalsStatus', 'loading', 'Обновление...');
showResult('goalsResult', null, false, true);
try {
const response = await fetch(`${getApiUrl()}/weekly_goals/setup`, {
method: 'POST',
headers: getAuthHeaders()
});
if (handleAuthError(response)) {
return;
}
const data = await response.json();
if (response.ok) {
showStatus('goalsStatus', 'success', 'Успешно');
showResult('goalsResult', data, false);
} else {
showStatus('goalsStatus', 'error', 'Ошибка');
showResult('goalsResult', data, true);
}
} catch (error) {
showStatus('goalsStatus', 'error', 'Ошибка');
showResult('goalsResult', { error: error.message }, true);
}
}
async function refreshProjectScoreSampleMv() {
showStatus('mvStatus', 'loading', 'Обновление...');
showResult('mvResult', null, false, true);
try {
const response = await fetch(`${getApiUrl()}/project_score_sample_mv/refresh`, {
method: 'POST',
headers: getAuthHeaders()
});
if (handleAuthError(response)) {
return;
}
const data = await response.json();
if (response.ok) {
showStatus('mvStatus', 'success', 'Успешно');
showResult('mvResult', data, false);
} else {
showStatus('mvStatus', 'error', 'Ошибка');
showResult('mvResult', data, true);
}
} catch (error) {
showStatus('mvStatus', 'error', 'Ошибка');
showResult('mvResult', { error: error.message }, true);
}
}
</script>
</body>
</html>