Add user auth, personal cabinet, admin panel and first admin bootstrap

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 22:20:09 +03:00
parent c6a7ecfc4c
commit 61e7290ce8
26 changed files with 1351 additions and 108 deletions
+5
View File
@@ -0,0 +1,5 @@
<nav class="admin-nav">
<a href="{{ url_for('admin.dashboard') }}" class="admin-nav__link {% if request.endpoint == 'admin.dashboard' %}admin-nav__link--active{% endif %}">Обзор</a>
<a href="{{ url_for('admin.users') }}" class="admin-nav__link {% if request.endpoint == 'admin.users' %}admin-nav__link--active{% endif %}">Пользователи</a>
<a href="{{ url_for('admin.photos') }}" class="admin-nav__link {% if request.endpoint == 'admin.photos' %}admin-nav__link--active{% endif %}">Фото</a>
</nav>
+80
View File
@@ -0,0 +1,80 @@
{% extends "base.html" %}
{% from "macros.html" import format_size %}
{% block title %}Админка — PhotoHost{% endblock %}
{% block content %}
<section class="page-header page-header--admin">
<div class="container">
<h1 class="page-header__title">Панель администратора</h1>
<p class="page-header__subtitle">Управление пользователями и контентом</p>
</div>
</section>
<section class="admin-section">
<div class="container">
{% include "admin/_nav.html" %}
{% include "partials/alerts.html" %}
<div class="admin-stats">
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{{ stats.users }}</span>
<span class="stat-card__label">пользователей</span>
</div>
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{{ stats.photos }}</span>
<span class="stat-card__label">фотографий</span>
</div>
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{{ stats.admins }}</span>
<span class="stat-card__label">администраторов</span>
</div>
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{{ format_size(stats.storage) }}</span>
<span class="stat-card__label">хранилище</span>
</div>
</div>
<div class="admin-grid">
<div class="admin-panel">
<h2 class="admin-panel__title">Новые пользователи</h2>
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>Логин</th>
<th>Email</th>
<th>Дата</th>
</tr>
</thead>
<tbody>
{% for user in recent_users %}
<tr>
<td>{{ user.username }}{% if user.is_admin %} <span class="badge badge--admin">admin</span>{% endif %}</td>
<td>{{ user.email }}</td>
<td>{{ user.created_at.strftime('%d.%m.%Y') }}</td>
</tr>
{% else %}
<tr><td colspan="3">Нет пользователей</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="admin-panel">
<h2 class="admin-panel__title">Последние фото</h2>
<div class="admin-mini-gallery">
{% for photo in recent_photos %}
<a href="{{ photo.url }}" target="_blank" class="admin-mini-gallery__item">
<img src="{{ photo.url }}" alt="{{ photo.original_name }}">
</a>
{% else %}
<p class="admin-empty">Нет фотографий</p>
{% endfor %}
</div>
</div>
</div>
</div>
</section>
{% endblock %}
+22
View File
@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}Фото — Админка{% endblock %}
{% block content %}
<section class="page-header page-header--admin">
<div class="container">
<h1 class="page-header__title">Все фотографии</h1>
</div>
</section>
<section class="admin-section">
<div class="container">
{% include "admin/_nav.html" %}
{% include "partials/alerts.html" %}
{% with photos=photos, show_owner=true, delete_mode='admin', empty_title='Нет фотографий', empty_text='Пользователи ещё не загружали фото' %}
{% include "partials/photo_gallery.html" %}
{% endwith %}
</div>
</section>
{% endblock %}
+79
View File
@@ -0,0 +1,79 @@
{% extends "base.html" %}
{% block title %}Пользователи — Админка{% endblock %}
{% block content %}
<section class="page-header page-header--admin">
<div class="container">
<h1 class="page-header__title">Пользователи</h1>
</div>
</section>
<section class="admin-section">
<div class="container">
{% include "admin/_nav.html" %}
{% include "partials/alerts.html" %}
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>Логин</th>
<th>Email</th>
<th>Фото</th>
<th>Роль</th>
<th>Статус</th>
<th>Дата</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.photo_count }}</td>
<td>
{% if user.is_admin %}
<span class="badge badge--admin">Админ</span>
{% else %}
<span class="badge">User</span>
{% endif %}
</td>
<td>
{% if user.is_active %}
<span class="badge badge--success">Активен</span>
{% else %}
<span class="badge badge--danger">Заблокирован</span>
{% endif %}
</td>
<td>{{ user.created_at.strftime('%d.%m.%Y') }}</td>
<td class="admin-actions">
{% if user.id != current_user.id %}
<form action="{{ url_for('admin.toggle_admin', user_id=user.id) }}" method="post">
<button type="submit" class="btn btn--ghost btn--sm">
{% if user.is_admin %}Снять admin{% else %}Сделать admin{% endif %}
</button>
</form>
<form action="{{ url_for('admin.toggle_active', user_id=user.id) }}" method="post">
<button type="submit" class="btn btn--ghost btn--sm">
{% if user.is_active %}Блок{% else %}Разблок{% endif %}
</button>
</form>
<form action="{{ url_for('admin.delete_user', user_id=user.id) }}" method="post" onsubmit="return confirm('Удалить пользователя и все его фото?');">
<button type="submit" class="btn btn--danger btn--sm">Удалить</button>
</form>
{% else %}
<span class="text-muted">Вы</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</section>
{% endblock %}