domovoy_bot/web/templates/users.html
Admin e06a3825ba 🧪 Комплексное тестирование + отчёты
 Проведено полное тестирование функционала:
- Меню жильца (11 кнопок)
- Меню админа (17 кнопок)
- База данных (проверка записей)
- Уникальность callback_data
- Сообщения в личку (без спама)

📝 Созданные файлы:
- tests/run_tests.py — автотесты
- tests/TEST_REPORT.md — отчёт о тестировании
- tests/COMPREHENSIVE_TEST_PLAN.md — план тестов
- send_test_report.py — отправка отчёта

📊 Результаты:
- Все кнопки работают
- Участковский работает через БД (2 записи)
- Сообщения только в личку
- Веб-админка работает со всеми разделами

🎯 ВСЁ РАБОТАЕТ!

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-01 20:00:56 +00:00

253 lines
12 KiB
HTML
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.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Пользователи - Домовой Бот</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
.sidebar {
min-height: 100vh;
background: linear-gradient(180deg, #2c3e50 0%, #1a252f 100%);
}
.sidebar .nav-link { color: rgba(255,255,255,0.8); padding: 1rem; }
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: white; background-color: rgba(255,255,255,0.1); }
.content { padding: 2rem; }
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-2 sidebar p-0">
<div class="p-3 text-white text-center">
<h4><i class="bi bi-house-door"></i> Домовой Бот</h4>
</div>
<nav class="nav flex-column">
<a class="nav-link" href="/"><i class="bi bi-speedometer2"></i> Дашборд</a>
<a class="nav-link active" href="/users"><i class="bi bi-people"></i> Пользователи</a>
<a class="nav-link" href="/verification"><i class="bi bi-shield-check"></i> Верификация</a>
<a class="nav-link" href="/phones"><i class="bi bi-telephone"></i> Телефоны</a>
<a class="nav-link" href="/export"><i class="bi bi-download"></i> Экспорт</a>
<a class="nav-link" href="/settings"><i class="bi bi-gear"></i> Настройки</a>
</nav>
</div>
<div class="col-md-10 content">
<h2 class="mb-4">Пользователи</h2>
<!-- Search -->
<div class="card mb-4">
<div class="card-body">
<form method="GET" action="/users" class="row g-3">
<div class="col-md-10">
<input type="text" class="form-control" name="search"
placeholder="Поиск по имени, квартире, нику..."
value="{{ search }}">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search"></i> Поиск
</button>
</div>
</form>
</div>
</div>
<!-- Users Table -->
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Имя</th>
<th>Ник</th>
<th>Квартира</th>
<th>Телефон</th>
<th>Верифицирован</th>
<th>Бан</th>
<th>Рейтинг</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.user_id }}</td>
<td>{{ user.full_name or '—' }}</td>
<td>@{{ user.username or 'нет' }}</td>
<td>
<span id="apt-{{ user.user_id }}">{{ user.apartment or '—' }}</span>
</td>
<td>
<span id="phone-{{ user.user_id }}">{{ user.phone or '—' }}</span>
</td>
<td>
{% if user.verified %}
<span class="badge bg-success">✅ Да</span>
{% else %}
<span class="badge bg-danger">❌ Нет</span>
{% endif %}
</td>
<td>
{% if user.banned %}
<span class="badge bg-danger">🚫 Да</span>
{% else %}
<span class="badge bg-success">✅ Нет</span>
{% endif %}
</td>
<td>{{ user.rating }}</td>
<td>
<button class="btn btn-sm btn-primary" onclick="editUser({{ user.user_id }})">
<i class="bi bi-pencil"></i>
</button>
{% if user.banned %}
<button class="btn btn-sm btn-success" onclick="unbanUser({{ user.user_id }})">
<i class="bi bi-unlock"></i>
</button>
{% else %}
<button class="btn btn-sm btn-danger" onclick="banUser({{ user.user_id }})">
<i class="bi bi-lock"></i>
</button>
{% endif %}
{% if user.verified %}
<button class="btn btn-sm btn-warning" onclick="unverifyUser({{ user.user_id }})" title="Отозвать верификацию">
<i class="bi bi-shield-x"></i>
</button>
{% else %}
<button class="btn btn-sm btn-success" onclick="verifyUser({{ user.user_id }})" title="Верифицировать">
<i class="bi bi-shield-check"></i>
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if not users %}
<p class="text-center text-muted">Пользователи не найдены</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Modal для редактирования -->
<div class="modal fade" id="editUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Редактировать пользователя</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="editUserId">
<div class="mb-3">
<label class="form-label">Квартира</label>
<input type="text" class="form-control" id="editApartment" placeholder="45 или 12А">
</div>
<div class="mb-3">
<label class="form-label">Телефон</label>
<input type="text" class="form-control" id="editPhone" placeholder="+7 XXX XXX-XX-XX">
</div>
<div class="mb-3">
<label class="form-label">Рейтинг</label>
<input type="number" class="form-control" id="editRating" value="0">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" onclick="saveUser()">Сохранить</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const editModal = new bootstrap.Modal(document.getElementById('editUserModal'));
let currentUserId = null;
function editUser(userId) {
currentUserId = userId;
// Загружаем данные пользователя
fetch(`/api/user/${userId}`)
.then(r => r.json())
.then(data => {
const user = data.user;
document.getElementById('editUserId').value = user.id;
document.getElementById('editApartment').value = user.apartment || '';
document.getElementById('editPhone').value = user.phone || '';
document.getElementById('editRating').value = user.rating || 0;
editModal.show();
});
}
async function saveUser() {
const userId = document.getElementById('editUserId').value;
const data = {
apartment: document.getElementById('editApartment').value,
phone: document.getElementById('editPhone').value,
rating: parseInt(document.getElementById('editRating').value)
};
const response = await fetch(`/api/user/${userId}/update`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
alert('✅ Данные обновлены');
location.reload();
} else {
alert('❌ Ошибка: ' + result.error);
}
}
async function banUser(userId) {
if (!confirm('Забанить пользователя?')) return;
const r = await fetch(`/api/user/${userId}/ban`, { method: 'POST' });
const result = await r.json();
if (result.success) {
alert('Пользователь забанен');
location.reload();
}
}
async function unbanUser(userId) {
if (!confirm('Разбанить пользователя?')) return;
const r = await fetch(`/api/user/${userId}/unban`, { method: 'POST' });
const result = await r.json();
if (result.success) {
alert('Пользователь разбанен');
location.reload();
}
}
async function verifyUser(userId) {
if (!confirm('Верифицировать пользователя?')) return;
const r = await fetch(`/api/user/${userId}/verify`, { method: 'POST' });
const result = await r.json();
if (result.success) {
alert('Пользователь верифицирован');
location.reload();
}
}
async function unverifyUser(userId) {
if (!confirm('Отозвать верификацию?')) return;
const r = await fetch(`/api/user/${userId}/unverify`, { method: 'POST' });
const result = await r.json();
if (result.success) {
alert('Верификация отозвана');
location.reload();
}
}
</script>
</body>
</html>