domovoy_bot/web/templates/users.html
Admin d81ee6d238 АВТОВЕРИФИКАЦИЯ АКТИВНЫХ ПОЛЬЗОВАТЕЛЕЙ
 Реализовано:
- Автоверификация по 50+ сообщениям
- Кнопка админу ' Автоверификация'
- Веб-админка → Пользователи → Статистика + Кнопка
- Плавный ввод верификации

🎯 Как работает:
1. Бот считает сообщения пользователей
2. Кто набрал 50+ сообщений — считается 'своим'
3. Админ жмёт кнопку → все активные верифицируются
4. Веб-админка показывает статистику:
   - Всего пользователей
   - Верифицировано
   - Не верифицировано
   - Активных для верификации

📝 Преимущества:
- Не нужно проверять 200 человек вручную
- Активные жильцы сразу получают доступ
- Админы проверяют только подозрительных
- Мотивация: телефоны только после верификации

🧪 Тест:
/admin →  Автоверификация
или
Веб-админка → Пользователи → Верифицировать всех

💡 Плавный ввод:
- Бот предлагает верификацию, но не требует
- Телефоны и функции только после верификации
- Жильцы сами мотивированы пройти верификацию

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-03 18:41:29 +00:00

335 lines
16 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>
<!-- Stats -->
{% if stats %}
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body text-center">
<h3>{{ stats.total }}</h3>
<small>Всего пользователей</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body text-center">
<h3>{{ stats.verified }}</h3>
<small>Верифицировано</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-dark">
<div class="card-body text-center">
<h3>{{ stats.unverified }}</h3>
<small>Не верифицировано</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body text-center">
<h3>{{ stats.active_unverified }}</h3>
<small>Активных (50+ сообщений)</small>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Auto Verify Button -->
{% if stats and stats.active_unverified > 0 %}
<div class="card mb-4 bg-light">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<h5 class="mb-0">⚡ Автоверификация активных</h5>
<small class="text-muted">
Верифицировать {{ stats.active_unverified }} пользователей с 50+ сообщениями
</small>
</div>
<div class="col-md-4 text-end">
<button class="btn btn-success" onclick="autoVerify()">
<i class="bi bi-lightning-charge"></i> Верифицировать всех
</button>
</div>
</div>
</div>
</div>
{% endif %}
<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();
}
}
async function autoVerify() {
if (!confirm('Верифицировать всех активных пользователей (50+ сообщений)?')) return;
const resultDiv = document.getElementById('autoVerifyResult');
if (resultDiv) {
resultDiv.innerHTML = '<div class="alert alert-info">Автоверификация...</div>';
} else {
alert('Автоверификация...');
}
try {
const r = await fetch('/api/users/auto_verify', { method: 'POST' });
const result = await r.json();
if (result.success) {
alert('✅ ' + result.message);
location.reload();
} else {
alert('❌ Ошибка: ' + (result.error || 'Неизвестная'));
}
} catch (err) {
alert('❌ Ошибка: ' + err.message);
}
}
</script>
</body>
</html>