Release v2.1: GDPR, passkeys, session management, admin redesign
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,9 +1,23 @@
|
||||
<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.groups') }}" class="admin-nav__link {% if request.endpoint in ['admin.groups', 'admin.edit_group', 'admin.delete_group'] %}admin-nav__link--active{% endif %}">Группы</a>
|
||||
<a href="{{ url_for('admin.banners') }}" class="admin-nav__link {% if request.endpoint in ['admin.banners', 'admin.edit_banner', 'admin.delete_banner', 'admin.toggle_banner'] %}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>
|
||||
<a href="{{ url_for('admin.deploy') }}" class="admin-nav__link {% if request.endpoint == 'admin.deploy' %}admin-nav__link--active{% endif %}">Версии Git</a>
|
||||
<a href="{{ url_for('admin.settings') }}" class="admin-nav__link {% if request.endpoint == 'admin.settings' %}admin-nav__link--active{% endif %}">Настройки</a>
|
||||
<a href="{{ url_for('admin.dashboard') }}" class="admin-nav__link {% if request.endpoint == 'admin.dashboard' %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">📊</span> Обзор
|
||||
</a>
|
||||
<a href="{{ url_for('admin.users') }}" class="admin-nav__link {% if request.endpoint == 'admin.users' %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">👥</span> Пользователи
|
||||
</a>
|
||||
<a href="{{ url_for('admin.groups') }}" class="admin-nav__link {% if request.endpoint in ['admin.groups', 'admin.edit_group', 'admin.delete_group'] %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">🏷️</span> Группы
|
||||
</a>
|
||||
<a href="{{ url_for('admin.banners') }}" class="admin-nav__link {% if request.endpoint in ['admin.banners', 'admin.edit_banner', 'admin.delete_banner', 'admin.toggle_banner'] %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">📢</span> Баннеры
|
||||
</a>
|
||||
<a href="{{ url_for('admin.photos') }}" class="admin-nav__link {% if request.endpoint == 'admin.photos' %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">🖼️</span> Фото
|
||||
</a>
|
||||
<a href="{{ url_for('admin.deploy') }}" class="admin-nav__link {% if request.endpoint == 'admin.deploy' %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">🚀</span> Версии Git
|
||||
</a>
|
||||
<a href="{{ url_for('admin.settings') }}" class="admin-nav__link {% if request.endpoint == 'admin.settings' %}admin-nav__link--active{% endif %}">
|
||||
<span class="admin-nav__icon">🔧</span> Настройки
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
|
||||
{% block title %}Рекламные баннеры — Админка{% endblock %}
|
||||
{% block admin_title %}Рекламные баннеры{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">Баннеры на главной, в кабинете и в подвале</p>{% 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" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<div class="admin-panel folder-create">
|
||||
<h2 class="admin-panel__title">Добавить баннер</h2>
|
||||
<form method="post" class="auth-form folder-create__form">
|
||||
@@ -121,6 +111,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,92 +1,80 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
{% from "macros.html" import format_size %}
|
||||
|
||||
{% block title %}Админка — PhotoHost{% endblock %}
|
||||
{% block admin_title %}Обзор{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">Статистика и последние действия</p>{% 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>
|
||||
{% block admin_content %}
|
||||
<div class="admin-stats admin-stats--cards">
|
||||
<div class="admin-stat-card">
|
||||
<span class="admin-stat-card__value">{{ stats.users }}</span>
|
||||
<span class="admin-stat-card__label">Пользователей</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="admin-stat-card">
|
||||
<span class="admin-stat-card__value">{{ stats.photos }}</span>
|
||||
<span class="admin-stat-card__label">Фотографий</span>
|
||||
</div>
|
||||
<div class="admin-stat-card">
|
||||
<span class="admin-stat-card__value">{{ stats.admins }}</span>
|
||||
<span class="admin-stat-card__label">Администраторов</span>
|
||||
</div>
|
||||
<div class="admin-stat-card">
|
||||
<span class="admin-stat-card__value">{{ stats.groups }}</span>
|
||||
<span class="admin-stat-card__label">Групп</span>
|
||||
</div>
|
||||
<div class="admin-stat-card admin-stat-card--accent">
|
||||
<span class="admin-stat-card__value">{{ format_size(stats.storage) }}</span>
|
||||
<span class="admin-stat-card__label">Хранилище</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="admin-section">
|
||||
<div class="container">
|
||||
{% include "admin/_nav.html" %}
|
||||
{% include "partials/alerts.html" %}
|
||||
{% if current_version %}
|
||||
<p class="admin-version-bar">
|
||||
Версия Git: <strong>{{ current_version }}</strong>
|
||||
· <a href="{{ url_for('admin.deploy') }}">Управление версиями</a>
|
||||
{% if not deploy_enabled %}<span class="badge badge--muted">deploy off</span>{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<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">{{ stats.groups }}</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>
|
||||
|
||||
{% if current_version %}
|
||||
<p class="folder-hint" style="margin-bottom: 24px;">
|
||||
Версия Git: <strong>{{ current_version }}</strong>
|
||||
· <a href="{{ url_for('admin.deploy') }}">Управление версиями</a>
|
||||
{% if not deploy_enabled %}(deploy выключен){% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
<div class="admin-grid">
|
||||
<div class="admin-panel admin-panel--elevated">
|
||||
<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 %}
|
||||
<p class="admin-empty">Нет фотографий</p>
|
||||
<tr><td colspan="3">Нет пользователей</td></tr>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="admin-panel admin-panel--elevated">
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
|
||||
{% block title %}Версии Git — Админка{% endblock %}
|
||||
{% block admin_title %}Обновление Git{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">Переключение релизов и пересборка Docker</p>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="page-header page-header--admin">
|
||||
<div class="container">
|
||||
<h1 class="page-header__title">Обновление и версии Git</h1>
|
||||
<p class="page-header__subtitle">Переключение между релизами и пересборка Docker</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="admin-section">
|
||||
<div class="container">
|
||||
{% include "admin/_nav.html" %}
|
||||
{% include "partials/alerts.html" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<div class="admin-stats">
|
||||
<div class="stat-card stat-card--admin">
|
||||
<span class="stat-card__value">{{ status.current or '—' }}</span>
|
||||
@@ -87,6 +77,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
{% from "macros.html" import format_size %}
|
||||
|
||||
{% block title %}Группы — Админка{% endblock %}
|
||||
{% block admin_title %}Группы пользователей{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">Квоты диска, лимиты папок и фото</p>{% 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" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<div class="admin-panel folder-create">
|
||||
<h2 class="admin-panel__title">Создать группу</h2>
|
||||
<form method="post" class="auth-form folder-create__form">
|
||||
@@ -86,6 +76,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="admin-shell">
|
||||
<aside class="admin-sidebar">
|
||||
<div class="admin-sidebar__head">
|
||||
<span class="admin-sidebar__logo">⚙️</span>
|
||||
<div>
|
||||
<strong>PhotoHost</strong>
|
||||
<span>Админ-панель</span>
|
||||
</div>
|
||||
</div>
|
||||
{% include "admin/_nav.html" %}
|
||||
<a href="{{ url_for('main.index') }}" class="admin-sidebar__back">← На сайт</a>
|
||||
</aside>
|
||||
<div class="admin-main">
|
||||
<div class="admin-main__header">
|
||||
<div>
|
||||
<h1 class="admin-main__title">{% block admin_title %}Админка{% endblock %}</h1>
|
||||
{% block admin_subtitle %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% include "partials/alerts.html" %}
|
||||
{% block admin_content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,22 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
|
||||
{% block title %}Фото — Админка{% endblock %}
|
||||
{% block admin_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" %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% with photos=photos, show_owner=true, delete_mode='admin', empty_title='Нет фотографий', empty_text='Пользователи ещё не загружали фото' %}
|
||||
{% include "partials/photo_gallery.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
|
||||
{% block title %}Настройки — Админка{% endblock %}
|
||||
{% block admin_title %}Настройки системы{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">S3, SFTP, FTP, SMTP и лимиты загрузки</p>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="page-header page-header--admin">
|
||||
<div class="container">
|
||||
<h1 class="page-header__title">Настройки системы</h1>
|
||||
<p class="page-header__subtitle">S3, SFTP, FTP, SMTP и лимиты загрузки</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="admin-section">
|
||||
<div class="container">
|
||||
{% include "admin/_nav.html" %}
|
||||
{% include "partials/alerts.html" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<form method="post" class="settings-form">
|
||||
<input type="hidden" name="action" value="save">
|
||||
|
||||
@@ -88,6 +78,4 @@
|
||||
<input type="hidden" name="action" value="test_smtp">
|
||||
<button type="submit" class="btn btn--ghost">Отправить тестовое письмо на {{ current_user.email }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "admin/layout.html" %}
|
||||
|
||||
{% block title %}Пользователи — Админка{% endblock %}
|
||||
{% block admin_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" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
@@ -84,6 +74,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
<button type="submit" class="btn btn--primary btn--full">Войти</button>
|
||||
</form>
|
||||
|
||||
<button type="button" class="btn btn--ghost btn--full" id="passkeyLoginBtn" style="margin-top:12px">
|
||||
Войти с Passkey
|
||||
</button>
|
||||
|
||||
<p class="auth-card__footer">
|
||||
<a href="{{ url_for('auth.forgot_password') }}">Забыли пароль?</a> ·
|
||||
<a href="{{ url_for('auth.register') }}">Зарегистрироваться</a>
|
||||
@@ -35,3 +39,7 @@
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/passkey.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -49,11 +49,18 @@
|
||||
<footer class="footer">
|
||||
<div class="container footer__inner">
|
||||
<p>PhotoHost — Python + PostgreSQL + Docker</p>
|
||||
<p class="footer__links">
|
||||
<a href="{{ url_for('legal.privacy') }}">Конфиденциальность</a> ·
|
||||
<a href="{{ url_for('legal.cookies') }}">Cookies</a> ·
|
||||
<a href="{{ url_for('legal.gdpr') }}">GDPR</a>
|
||||
</p>
|
||||
<p class="footer__muted">Храните и делитесь фотографиями просто</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{% include "partials/cookie_banner.html" %}
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/cookie-consent.js') }}"></script>
|
||||
{% include "partials/share_modal.html" %}
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<section class="page-header">
|
||||
<div class="container">
|
||||
<h1 class="page-header__title">Настройки профиля</h1>
|
||||
<p class="page-header__subtitle">Измените email или пароль</p>
|
||||
<p class="page-header__subtitle">Безопасность, passkey, сессии и GDPR</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="auth-section">
|
||||
<div class="container auth-container">
|
||||
<div class="auth-card auth-card--wide">
|
||||
<section class="auth-section profile-section">
|
||||
<div class="container profile-grid">
|
||||
<div class="auth-card auth-card--wide profile-card">
|
||||
{% include "partials/alerts.html" %}
|
||||
|
||||
<div class="profile-info">
|
||||
@@ -30,7 +30,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="profile-card__title">Email и пароль</h2>
|
||||
<form method="post" class="auth-form">
|
||||
<input type="hidden" name="action" value="save">
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required value="{{ current_user.email }}">
|
||||
@@ -41,19 +43,111 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password">Новый пароль (необязательно)</label>
|
||||
<input type="password" id="new_password" name="new_password" minlength="6" placeholder="оставьте пустым, если не меняете">
|
||||
<input type="password" id="new_password" name="new_password" minlength="6">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password2">Подтверждение нового пароля</label>
|
||||
<input type="password" id="new_password2" name="new_password2" minlength="6">
|
||||
</div>
|
||||
<button type="submit" class="btn btn--primary btn--full">Сохранить</button>
|
||||
<button type="submit" class="btn btn--primary">Сохранить</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-card__footer">
|
||||
<a href="{{ url_for('cabinet.index') }}">← Вернуться в кабинет</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="auth-card auth-card--wide profile-card">
|
||||
<h2 class="profile-card__title">Passkey</h2>
|
||||
<p class="profile-card__hint">Вход без пароля через Face ID, Touch ID, Windows Hello или ключ безопасности.</p>
|
||||
|
||||
{% if passkeys %}
|
||||
<ul class="session-list">
|
||||
{% for passkey in passkeys %}
|
||||
<li class="session-item">
|
||||
<div>
|
||||
<strong>{{ passkey.name }}</strong>
|
||||
<span class="session-item__meta">Добавлен {{ passkey.created_at.strftime('%d.%m.%Y') }}</span>
|
||||
</div>
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="delete_passkey">
|
||||
<input type="hidden" name="passkey_id" value="{{ passkey.id }}">
|
||||
<button type="submit" class="btn btn--danger btn--sm">Удалить</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="profile-card__empty">Passkey не настроен</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group" style="margin-top:16px">
|
||||
<label for="passkeyName">Название устройства</label>
|
||||
<input type="text" id="passkeyName" value="Моё устройство" maxlength="120">
|
||||
</div>
|
||||
<button type="button" class="btn btn--ghost" id="addPasskeyBtn">Добавить passkey</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-card auth-card--wide profile-card">
|
||||
<h2 class="profile-card__title">Активные сессии</h2>
|
||||
<p class="profile-card__hint">Все устройства, где выполнен вход в ваш аккаунт.</p>
|
||||
|
||||
{% if sessions %}
|
||||
<ul class="session-list">
|
||||
{% for item in sessions %}
|
||||
<li class="session-item {% if item.session_key == current_sid %}session-item--current{% endif %}">
|
||||
<div>
|
||||
<strong>{{ item.device_label }}</strong>
|
||||
{% if item.session_key == current_sid %}<span class="badge badge--success">текущая</span>{% endif %}
|
||||
<span class="session-item__meta">
|
||||
{{ item.ip_address or 'IP неизвестен' }} ·
|
||||
{{ item.last_seen_at.strftime('%d.%m.%Y %H:%M') }}
|
||||
</span>
|
||||
</div>
|
||||
{% if item.session_key != current_sid %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="revoke_session">
|
||||
<input type="hidden" name="session_id" value="{{ item.id }}">
|
||||
<button type="submit" class="btn btn--ghost btn--sm">Завершить</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form method="post" style="margin-top:16px">
|
||||
<input type="hidden" name="action" value="revoke_all_sessions">
|
||||
<button type="submit" class="btn btn--danger btn--sm" onclick="return confirm('Завершить все сессии кроме текущей?');">
|
||||
Выйти на всех устройствах
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="profile-card__empty">Нет активных сессий</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="auth-card auth-card--wide profile-card">
|
||||
<h2 class="profile-card__title">GDPR и данные</h2>
|
||||
<p class="profile-card__hint">
|
||||
Вы можете скачать копию своих данных или удалить аккаунт.
|
||||
<a href="{{ url_for('legal.gdpr') }}">Подробнее о правах</a>
|
||||
</p>
|
||||
<div class="profile-actions">
|
||||
<a href="{{ url_for('cabinet.export_profile') }}" class="btn btn--ghost">Скачать мои данные (JSON)</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="profile-delete-form" onsubmit="return confirm('Удалить аккаунт без возможности восстановления?');">
|
||||
<input type="hidden" name="action" value="delete_account">
|
||||
<div class="form-group">
|
||||
<label for="delete_password">Пароль для удаления аккаунта</label>
|
||||
<input type="password" id="delete_password" name="delete_password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn--danger">Удалить аккаунт</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p class="auth-card__footer profile-footer">
|
||||
<a href="{{ url_for('cabinet.index') }}">← Вернуться в кабинет</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/passkey.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Политика cookies — PhotoHost{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="legal-section">
|
||||
<div class="container legal-container">
|
||||
<h1>Политика cookies</h1>
|
||||
|
||||
<h2>Обязательные cookies</h2>
|
||||
<p>Нужны для входа, CSRF-защиты и работы личного кабинета. Отключить их нельзя.</p>
|
||||
<ul>
|
||||
<li><code>session</code> — идентификатор сессии Flask</li>
|
||||
<li><code>photohost_consent</code> — ваш выбор по cookies</li>
|
||||
</ul>
|
||||
|
||||
<h2>Аналитические cookies</h2>
|
||||
<p>Используются только при вашем согласии. Помогают понять, как улучшать сервис.</p>
|
||||
|
||||
<h2>Управление согласием</h2>
|
||||
<p>Вы можете изменить выбор через баннер cookies или в профиле после входа.</p>
|
||||
|
||||
<p><a href="{{ url_for('legal.privacy') }}">Политика конфиденциальности</a></p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}GDPR — PhotoHost{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="legal-section">
|
||||
<div class="container legal-container">
|
||||
<h1>GDPR — ваши права</h1>
|
||||
<p>В соответствии с Общим регламентом защиты данных (EU GDPR) вы имеете следующие права:</p>
|
||||
|
||||
<div class="legal-cards">
|
||||
<article class="legal-card">
|
||||
<h3>Право на доступ</h3>
|
||||
<p>Узнать, какие данные мы храним о вас.</p>
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h3>Право на исправление</h3>
|
||||
<p>Обновить email и пароль в профиле.</p>
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h3>Право на переносимость</h3>
|
||||
<p>Скачать данные в JSON из <a href="{{ url_for('cabinet.profile') }}">профиля</a>.</p>
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h3>Право на удаление</h3>
|
||||
<p>Удалить аккаунт и все связанные фото в профиле.</p>
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h3>Право на ограничение</h3>
|
||||
<p>Отключить аналитические cookies в баннере согласия.</p>
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h3>Право отозвать согласие</h3>
|
||||
<p>Изменить настройки cookies в любой момент.</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<h2>Как воспользоваться</h2>
|
||||
<ol>
|
||||
<li>Войдите в аккаунт</li>
|
||||
<li>Откройте <a href="{{ url_for('cabinet.profile') }}">Профиль</a></li>
|
||||
<li>Экспортируйте данные или удалите аккаунт</li>
|
||||
</ol>
|
||||
|
||||
<p>Для запросов к администратору используйте email, указанный при регистрации.</p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Политика конфиденциальности — PhotoHost{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="legal-section">
|
||||
<div class="container legal-container">
|
||||
<h1>Политика конфиденциальности</h1>
|
||||
<p class="legal-updated">Последнее обновление: {{ "2026-06-06" }}</p>
|
||||
|
||||
<h2>1. Какие данные мы обрабатываем</h2>
|
||||
<ul>
|
||||
<li>Учётные данные: имя пользователя, email, хеш пароля</li>
|
||||
<li>Загруженные фото и метаданные (имя файла, размер, дата)</li>
|
||||
<li>Технические данные: IP-адрес, user-agent, cookies сессии</li>
|
||||
<li>Passkey (публичный ключ WebAuthn, без хранения биометрии)</li>
|
||||
</ul>
|
||||
|
||||
<h2>2. Цели обработки</h2>
|
||||
<ul>
|
||||
<li>Регистрация, авторизация и предоставление сервиса</li>
|
||||
<li>Хранение и публикация загруженных изображений</li>
|
||||
<li>Безопасность аккаунта и управление сессиями</li>
|
||||
<li>Выполнение требований GDPR</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Правовые основания (GDPR)</h2>
|
||||
<p>Обработка осуществляется на основании исполнения договора (п. 6(1)(b) GDPR) и законного интереса по обеспечению безопасности (п. 6(1)(f) GDPR).</p>
|
||||
|
||||
<h2>4. Срок хранения</h2>
|
||||
<p>Данные хранятся до удаления аккаунта пользователем или до получения запроса на удаление.</p>
|
||||
|
||||
<h2>5. Ваши права</h2>
|
||||
<p>Вы можете запросить доступ, исправление, экспорт или удаление данных в <a href="{{ url_for('cabinet.profile') }}">профиле</a> или связавшись с администратором сайта.</p>
|
||||
|
||||
<p><a href="{{ url_for('legal.gdpr') }}">GDPR — подробнее о правах</a> · <a href="{{ url_for('legal.cookies') }}">Политика cookies</a></p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="cookie-banner" id="cookieBanner" hidden>
|
||||
<div class="cookie-banner__inner">
|
||||
<div class="cookie-banner__text">
|
||||
<strong>Мы используем cookies</strong>
|
||||
<p>
|
||||
Обязательные cookies нужны для входа и работы сайта.
|
||||
Аналитические cookies помогают улучшать сервис.
|
||||
<a href="{{ url_for('legal.cookies') }}">Политика cookies</a> ·
|
||||
<a href="{{ url_for('legal.privacy') }}">Конфиденциальность</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="cookie-banner__actions">
|
||||
<button type="button" class="btn btn--ghost btn--sm" id="cookieRejectBtn">Только необходимые</button>
|
||||
<button type="button" class="btn btn--primary btn--sm" id="cookieAcceptBtn">Принять все</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user