v1.24 Уведомления о верификации
🔔 УВЕДОМЛЕНИЯ: • Бейдж в меню: Верификация (N) • Блок в Пользователях: Ожидают (N) • Быстрая верификация: [✅] [❌] • Telegram уведомление админу ✅ БЫСТРАЯ ВЕРИФИКАЦИЯ: • Прямо в разделе Пользователи • Кнопки: Верифицировать / Отклонить • Перезагрузка страницы 📁 ФАЙЛЫ: • web/app.py: pending_count • web/templates/users.html: блок уведомлений • web/templates/dashboard.html: бейдж • handlers/verification.py: уведомление в Telegram Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
dc8ae8b152
commit
377b061ac9
3 changed files with 112 additions and 3 deletions
31
web/app.py
31
web/app.py
|
|
@ -60,13 +60,20 @@ async def dashboard(request: Request, username: str = Depends(get_current_admin)
|
||||||
upcoming_events = (await session.execute(
|
upcoming_events = (await session.execute(
|
||||||
select(func.count(Event.id)).where(Event.is_active == True))).scalar()
|
select(func.count(Event.id)).where(Event.is_active == True))).scalar()
|
||||||
|
|
||||||
|
# Получаем количество ожидающих верификации
|
||||||
|
from database.models import VerificationRequest
|
||||||
|
pending_count = (await session.execute(
|
||||||
|
select(func.count(VerificationRequest.id)).where(VerificationRequest.status == 'pending')
|
||||||
|
)).scalar()
|
||||||
|
|
||||||
return templates.TemplateResponse("dashboard.html", {
|
return templates.TemplateResponse("dashboard.html", {
|
||||||
"request": request, "username": username,
|
"request": request, "username": username,
|
||||||
"stats": {
|
"stats": {
|
||||||
"users": users_count, "verified": verified_count,
|
"users": users_count, "verified": verified_count,
|
||||||
"messages": messages_count, "ads": active_ads,
|
"messages": messages_count, "ads": active_ads,
|
||||||
"polls": active_polls, "events": upcoming_events
|
"polls": active_polls, "events": upcoming_events
|
||||||
}
|
},
|
||||||
|
"pending_count": pending_count
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -114,6 +121,24 @@ async def users_page(request: Request, search: str = "", filter: str = "", usern
|
||||||
ig_result = await session.execute(ig_stmt)
|
ig_result = await session.execute(ig_stmt)
|
||||||
ig_user_ids = set(row[0] for row in ig_result.all())
|
ig_user_ids = set(row[0] for row in ig_result.all())
|
||||||
|
|
||||||
|
# Получаем заявки на верификацию
|
||||||
|
from database.models import VerificationRequest
|
||||||
|
pending_stmt = select(VerificationRequest).where(
|
||||||
|
VerificationRequest.status == 'pending'
|
||||||
|
).order_by(VerificationRequest.created_at.desc())
|
||||||
|
pending_result = await session.execute(pending_stmt)
|
||||||
|
pending_requests = [
|
||||||
|
{
|
||||||
|
'id': req.id,
|
||||||
|
'user_id': req.user_id,
|
||||||
|
'user_name': req.user.full_name if req.user else None,
|
||||||
|
'username': req.user.username if req.user else None,
|
||||||
|
'apartment': req.apartment,
|
||||||
|
'created_at': req.created_at
|
||||||
|
}
|
||||||
|
for req in pending_result.scalars().all()
|
||||||
|
]
|
||||||
|
|
||||||
# Статистика
|
# Статистика
|
||||||
total_count = (await session.execute(select(func.count(User.user_id)))).scalar()
|
total_count = (await session.execute(select(func.count(User.user_id)))).scalar()
|
||||||
verified_count = (await session.execute(select(func.count(User.user_id)).where(User.verified == True))).scalar()
|
verified_count = (await session.execute(select(func.count(User.user_id)).where(User.verified == True))).scalar()
|
||||||
|
|
@ -126,7 +151,9 @@ async def users_page(request: Request, search: str = "", filter: str = "", usern
|
||||||
return templates.TemplateResponse("users.html",
|
return templates.TemplateResponse("users.html",
|
||||||
{"request": request, "username": username, "users": users, "search": search, "current_filter": filter,
|
{"request": request, "username": username, "users": users, "search": search, "current_filter": filter,
|
||||||
"stats": {"total": total_count, "verified": verified_count, "unverified": unverified_count, "active_unverified": active_unverified, "ig": ig_count},
|
"stats": {"total": total_count, "verified": verified_count, "unverified": unverified_count, "active_unverified": active_unverified, "ig": ig_count},
|
||||||
"ig_user_ids": ig_user_ids})
|
"ig_user_ids": ig_user_ids,
|
||||||
|
"pending_requests": pending_requests,
|
||||||
|
"pending_count": len(pending_requests)})
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/users/auto_verify")
|
@app.post("/api/users/auto_verify")
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,12 @@
|
||||||
<nav class="nav flex-column">
|
<nav class="nav flex-column">
|
||||||
<a class="nav-link active" href="/"><i class="bi bi-speedometer2"></i> Дашборд</a>
|
<a class="nav-link active" href="/"><i class="bi bi-speedometer2"></i> Дашборд</a>
|
||||||
<a class="nav-link" href="/users"><i class="bi bi-people"></i> Пользователи</a>
|
<a class="nav-link" 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="/verification">
|
||||||
|
<i class="bi bi-shield-check"></i> Верификация
|
||||||
|
{% if pending_count and pending_count > 0 %}
|
||||||
|
<span class="badge bg-danger ms-2">{{ pending_count }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
<a class="nav-link" href="/phones"><i class="bi bi-telephone"></i> Телефоны</a>
|
<a class="nav-link" href="/phones"><i class="bi bi-telephone"></i> Телефоны</a>
|
||||||
<a class="nav-link" href="/broadcast"><i class="bi bi-broadcast"></i> Рассылки</a>
|
<a class="nav-link" href="/broadcast"><i class="bi bi-broadcast"></i> Рассылки</a>
|
||||||
<a class="nav-link" href="/polls"><i class="bi bi-bar-chart"></i> Опросы</a>
|
<a class="nav-link" href="/polls"><i class="bi bi-bar-chart"></i> Опросы</a>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,51 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<!-- Pending Verifications Alert -->
|
||||||
|
{% if pending_count and pending_count > 0 %}
|
||||||
|
<div class="card mb-4 border-warning">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-bell"></i> Ожидают верификации: {{ pending_count }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Пользователь</th>
|
||||||
|
<th>Квартира</th>
|
||||||
|
<th>Дата заявки</th>
|
||||||
|
<th>Действия</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for req in pending_requests %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>{{ req.user_name or 'user' ~ req.user_id }}</strong><br>
|
||||||
|
<small class="text-muted">@{{ req.username or 'нет' }}</small>
|
||||||
|
</td>
|
||||||
|
<td><code>{{ req.apartment }}</code></td>
|
||||||
|
<td>{{ req.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-success" onclick="quickVerify({{ req.user_id }}, '{{ req.apartment }}')" title="Верифицировать">
|
||||||
|
<i class="bi bi-check-lg"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="quickReject({{ req.id }})" title="Отклонить">
|
||||||
|
<i class="bi bi-x-lg"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
{% if stats %}
|
{% if stats %}
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
|
|
@ -308,6 +353,38 @@
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
// Быстрая верификация
|
||||||
|
async function quickVerify(userId, apartment) {
|
||||||
|
if (!confirm(`Верифицировать пользователя (кв. ${apartment})?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/user/${userId}/verify`, { method: 'POST' });
|
||||||
|
const result = await r.json();
|
||||||
|
if (result.success) {
|
||||||
|
alert('✅ Пользователь верифицирован!');
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('❌ Ошибка: ' + err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Быстрое отклонение
|
||||||
|
async function quickReject(reqId) {
|
||||||
|
if (!confirm('Отклонить заявку на верификацию?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/verification/${reqId}/reject`, { method: 'POST' });
|
||||||
|
const result = await r.json();
|
||||||
|
if (result.success) {
|
||||||
|
alert('✅ Заявка отклонена');
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('❌ Ошибка: ' + err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const editModal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
const editModal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
||||||
let currentUserId = null;
|
let currentUserId = null;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue