Release 1.2: bulk upload, S3/SFTP/FTP, SMTP, password reset, user groups, git deploy

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 22:38:37 +03:00
parent db2cef41bb
commit c1aac7ecac
33 changed files with 1649 additions and 150 deletions
+3
View File
@@ -1,5 +1,8 @@
<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.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>
</nav>
+12
View File
@@ -29,12 +29,24 @@
<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>
+92
View File
@@ -0,0 +1,92 @@
{% extends "base.html" %}
{% block title %}Версии Git — Админка{% 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" %}
<div class="admin-stats">
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{{ status.current or '—' }}</span>
<span class="stat-card__label">текущая версия</span>
</div>
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{% if status.repo_ready %}OK{% else %}—{% endif %}</span>
<span class="stat-card__label">репозиторий</span>
</div>
<div class="stat-card stat-card--admin">
<span class="stat-card__value">{% if status.enabled %}ON{% else %}OFF{% endif %}</span>
<span class="stat-card__label">deploy из админки</span>
</div>
</div>
{% if not status.repo_ready %}
<div class="alert alert--error">
Git-репозиторий недоступен по пути <code>{{ status.repo_path }}</code>.
Смонтируйте проект в контейнер: <code>./:/repo</code> в docker-compose.yml
</div>
{% endif %}
{% if not status.enabled %}
<div class="alert alert--error">
Обновление через админку отключено. Установите <code>ALLOW_GIT_DEPLOY=true</code> в .env
</div>
{% endif %}
<div class="admin-grid">
<div class="admin-panel">
<h2 class="admin-panel__title">1. Обновить список версий</h2>
<p class="folder-hint">Путь: {{ status.repo_path }}{% if status.remote_url %} · {{ status.remote_url }}{% endif %}</p>
<form method="post">
<input type="hidden" name="action" value="fetch">
<button type="submit" class="btn btn--primary" {% if not status.repo_ready %}disabled{% endif %}>git fetch</button>
</form>
</div>
<div class="admin-panel">
<h2 class="admin-panel__title">2. Переключить версию</h2>
<form method="post" class="auth-form">
<input type="hidden" name="action" value="checkout">
<div class="form-group">
<label for="ref">Тег или ветка</label>
<input type="text" id="ref" name="ref" list="git-refs" required placeholder="v1.1">
<datalist id="git-refs">
{% for tag in status.tags %}
<option value="{{ tag }}">
{% endfor %}
{% for branch in status.branches %}
<option value="{{ branch }}">
{% endfor %}
</datalist>
</div>
<button type="submit" class="btn btn--primary" {% if not status.repo_ready %}disabled{% endif %}>git checkout</button>
</form>
{% if status.tags %}
<p class="folder-hint">Теги: {{ status.tags[:8]|join(', ') }}{% if status.tags|length > 8 %}…{% endif %}</p>
{% elif status.tags_error %}
<p class="folder-hint">{{ status.tags_error }}</p>
{% endif %}
</div>
<div class="admin-panel">
<h2 class="admin-panel__title">3. Пересобрать Docker</h2>
<p class="folder-hint">Требуется доступ к <code>/var/run/docker.sock</code> в контейнере web</p>
<form method="post" onsubmit="return confirm('Пересобрать и перезапустить контейнеры?');">
<input type="hidden" name="action" value="rebuild">
<button type="submit" class="btn btn--primary" {% if not status.enabled or not status.repo_ready %}disabled{% endif %}>docker compose up -d --build</button>
</form>
</div>
</div>
</div>
</section>
{% endblock %}
+75
View File
@@ -0,0 +1,75 @@
{% extends "base.html" %}
{% from "macros.html" import format_size %}
{% block title %}Группы — Админка{% 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-panel folder-create">
<h2 class="admin-panel__title">Создать группу</h2>
<form method="post" class="auth-form folder-create__form">
<div class="form-group">
<label for="name">Название</label>
<input type="text" id="name" name="name" required minlength="2" placeholder="VIP">
</div>
<div class="form-group">
<label for="disk_quota_mb">Квота (МБ, 0 = без лимита)</label>
<input type="number" id="disk_quota_mb" name="disk_quota_mb" min="0" value="500">
</div>
<button type="submit" class="btn btn--primary">Создать группу</button>
</form>
</div>
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>Группа</th>
<th>Квота</th>
<th>Пользователей</th>
<th>Занято</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for item in group_stats %}
{% set group = item.group %}
<tr>
<td>
{{ group.name }}
{% if group.is_default %}<span class="badge badge--success">по умолчанию</span>{% endif %}
</td>
<td>{{ group.quota_label }}</td>
<td>{{ group.user_count }}</td>
<td>{{ format_size(item.storage_used) }}</td>
<td>
<form method="post" action="{{ url_for('admin.edit_group', group_id=group.id) }}" class="group-edit-form">
<input type="text" name="name" value="{{ group.name }}" required minlength="2" class="form-inline-input">
<input type="number" name="disk_quota_mb" value="{{ group.disk_quota_mb }}" min="0" class="form-inline-input form-inline-input--sm">
<button type="submit" class="btn btn--ghost btn--sm">Сохранить</button>
</form>
{% if not group.is_default %}
<form method="post" action="{{ url_for('admin.delete_group', group_id=group.id) }}" style="margin-top:6px" onsubmit="return confirm('Удалить группу? Пользователи будут перенесены в группу по умолчанию.');">
<button type="submit" class="btn btn--danger btn--sm">Удалить</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</section>
{% endblock %}
+93
View File
@@ -0,0 +1,93 @@
{% extends "base.html" %}
{% block title %}Настройки — Админка{% 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" %}
<form method="post" class="settings-form">
<input type="hidden" name="action" value="save">
<div class="admin-panel">
<h2 class="admin-panel__title">Загрузка фото</h2>
<div class="form-group">
<label for="max_bulk_upload">Максимум файлов за раз (до 100)</label>
<input type="number" id="max_bulk_upload" name="max_bulk_upload" min="1" max="100" value="{{ settings.max_bulk_upload }}">
</div>
</div>
<div class="admin-panel" style="margin-top:24px">
<h2 class="admin-panel__title">Amazon S3 / совместимое хранилище</h2>
<label class="form-checkbox"><input type="checkbox" name="s3_enabled" {% if settings.s3_enabled %}checked{% endif %}><span>Включить S3</span></label>
<div class="settings-grid">
<div class="form-group"><label>Endpoint</label><input type="text" name="s3_endpoint" value="{{ settings.s3_endpoint or '' }}" placeholder="https://s3.amazonaws.com"></div>
<div class="form-group"><label>Bucket</label><input type="text" name="s3_bucket" value="{{ settings.s3_bucket or '' }}"></div>
<div class="form-group"><label>Access Key</label><input type="text" name="s3_access_key" value="{{ settings.s3_access_key or '' }}"></div>
<div class="form-group"><label>Secret Key</label><input type="password" name="s3_secret_key" placeholder="оставьте пустым, если не меняете"></div>
<div class="form-group"><label>Region</label><input type="text" name="s3_region" value="{{ settings.s3_region or 'us-east-1' }}"></div>
<div class="form-group"><label>Public URL (CDN)</label><input type="text" name="s3_public_url" value="{{ settings.s3_public_url or '' }}"></div>
</div>
</div>
<div class="admin-panel" style="margin-top:24px">
<h2 class="admin-panel__title">SFTP (резервная копия)</h2>
<label class="form-checkbox"><input type="checkbox" name="sftp_enabled" {% if settings.sftp_enabled %}checked{% endif %}><span>Включить SFTP</span></label>
<div class="settings-grid">
<div class="form-group"><label>Host</label><input type="text" name="sftp_host" value="{{ settings.sftp_host or '' }}"></div>
<div class="form-group"><label>Port</label><input type="number" name="sftp_port" value="{{ settings.sftp_port }}"></div>
<div class="form-group"><label>Username</label><input type="text" name="sftp_username" value="{{ settings.sftp_username or '' }}"></div>
<div class="form-group"><label>Password</label><input type="password" name="sftp_password" placeholder="оставьте пустым, если не меняете"></div>
<div class="form-group"><label>Remote path</label><input type="text" name="sftp_remote_path" value="{{ settings.sftp_remote_path or '/uploads' }}"></div>
</div>
</div>
<div class="admin-panel" style="margin-top:24px">
<h2 class="admin-panel__title">FTP</h2>
<label class="form-checkbox"><input type="checkbox" name="ftp_enabled" {% if settings.ftp_enabled %}checked{% endif %}><span>Включить FTP</span></label>
<label class="form-checkbox"><input type="checkbox" name="ftp_use_tls" {% if settings.ftp_use_tls %}checked{% endif %}><span>FTPS (TLS)</span></label>
<div class="settings-grid">
<div class="form-group"><label>Host</label><input type="text" name="ftp_host" value="{{ settings.ftp_host or '' }}"></div>
<div class="form-group"><label>Port</label><input type="number" name="ftp_port" value="{{ settings.ftp_port }}"></div>
<div class="form-group"><label>Username</label><input type="text" name="ftp_username" value="{{ settings.ftp_username or '' }}"></div>
<div class="form-group"><label>Password</label><input type="password" name="ftp_password" placeholder="оставьте пустым, если не меняете"></div>
<div class="form-group"><label>Remote path</label><input type="text" name="ftp_remote_path" value="{{ settings.ftp_remote_path or '/uploads' }}"></div>
</div>
</div>
<div class="admin-panel" style="margin-top:24px">
<h2 class="admin-panel__title">SMTP (email)</h2>
<label class="form-checkbox"><input type="checkbox" name="smtp_enabled" {% if settings.smtp_enabled %}checked{% endif %}><span>Включить SMTP</span></label>
<label class="form-checkbox"><input type="checkbox" name="smtp_use_tls" {% if settings.smtp_use_tls %}checked{% endif %}><span>TLS</span></label>
<div class="settings-grid">
<div class="form-group"><label>Host</label><input type="text" name="smtp_host" value="{{ settings.smtp_host or '' }}" placeholder="smtp.gmail.com"></div>
<div class="form-group"><label>Port</label><input type="number" name="smtp_port" value="{{ settings.smtp_port }}"></div>
<div class="form-group"><label>Username</label><input type="text" name="smtp_username" value="{{ settings.smtp_username or '' }}"></div>
<div class="form-group"><label>Password</label><input type="password" name="smtp_password" placeholder="оставьте пустым, если не меняете"></div>
<div class="form-group"><label>From email</label><input type="email" name="smtp_from_email" value="{{ settings.smtp_from_email or '' }}"></div>
<div class="form-group"><label>From name</label><input type="text" name="smtp_from_name" value="{{ settings.smtp_from_name or 'PhotoHost' }}"></div>
</div>
<p class="folder-hint">SMTP используется для сброса пароля, регистрации и уведомлений о загрузке.</p>
</div>
<div class="page-header__actions" style="margin-top:24px">
<button type="submit" class="btn btn--primary">Сохранить настройки</button>
</div>
</form>
<form method="post" style="margin-top:16px">
<input type="hidden" name="action" value="test_smtp">
<button type="submit" class="btn btn--ghost">Отправить тестовое письмо на {{ current_user.email }}</button>
</form>
</div>
</section>
{% endblock %}
+10
View File
@@ -21,6 +21,7 @@
<th>ID</th>
<th>Логин</th>
<th>Email</th>
<th>Группа</th>
<th>Фото</th>
<th>Роль</th>
<th>Статус</th>
@@ -34,6 +35,15 @@
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>
<form action="{{ url_for('admin.set_user_group', user_id=user.id) }}" method="post" class="group-assign-form">
<select name="group_id" class="form-select form-select--sm" onchange="this.form.submit()">
{% for group in groups %}
<option value="{{ group.id }}" {% if user.group_id == group.id %}selected{% endif %}>{{ group.name }}</option>
{% endfor %}
</select>
</form>
</td>
<td>{{ user.photo_count }}</td>
<td>
{% if user.is_admin %}
+23
View File
@@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}Сброс пароля — PhotoHost{% endblock %}
{% block content %}
<section class="auth-section">
<div class="container auth-container">
<div class="auth-card">
<h1 class="auth-card__title">Сброс пароля</h1>
<p class="auth-card__subtitle">Введите email — отправим ссылку для восстановления</p>
{% include "partials/alerts.html" %}
<form method="post" class="auth-form">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required autocomplete="email">
</div>
<button type="submit" class="btn btn--primary btn--full">Отправить ссылку</button>
</form>
<p class="auth-card__footer"><a href="{{ url_for('auth.login') }}">← Вернуться ко входу</a></p>
</div>
</div>
</section>
{% endblock %}
+2 -1
View File
@@ -28,7 +28,8 @@
</form>
<p class="auth-card__footer">
Нет аккаунта? <a href="{{ url_for('auth.register') }}">Зарегистрироваться</a>
<a href="{{ url_for('auth.forgot_password') }}">Забыли пароль?</a> ·
<a href="{{ url_for('auth.register') }}">Зарегистрироваться</a>
</p>
</div>
</div>
+25
View File
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Новый пароль — PhotoHost{% endblock %}
{% block content %}
<section class="auth-section">
<div class="container auth-container">
<div class="auth-card">
<h1 class="auth-card__title">Новый пароль</h1>
{% include "partials/alerts.html" %}
<form method="post" class="auth-form">
<div class="form-group">
<label for="password">Новый пароль</label>
<input type="password" id="password" name="password" required minlength="6">
</div>
<div class="form-group">
<label for="password2">Подтверждение</label>
<input type="password" id="password2" name="password2" required minlength="6">
</div>
<button type="submit" class="btn btn--primary btn--full">Сохранить пароль</button>
</form>
</div>
</div>
</section>
{% endblock %}
+3 -13
View File
@@ -27,19 +27,9 @@
<section id="upload" class="upload-section">
<div class="container">
<h2 class="section-title">Загрузить в папку</h2>
<form action="{{ url_for('main.upload') }}" method="post" enctype="multipart/form-data" class="upload-form" id="uploadForm">
<input type="hidden" name="folder_id" value="{{ folder.id }}">
<div class="dropzone" id="dropzone">
<input type="file" name="photo" id="photoInput" accept="image/png,image/jpeg,image/gif,image/webp,image/bmp" hidden>
<p class="dropzone__title">Перетащите фото сюда</p>
<p class="dropzone__hint">или нажмите для выбора файла</p>
<div class="dropzone__preview" id="preview" hidden>
<img id="previewImg" alt="Предпросмотр">
<span id="previewName"></span>
</div>
</div>
<button type="submit" class="btn btn--primary" id="submitBtn" disabled>Загрузить</button>
</form>
{% with folder_id=folder.id, max_bulk_upload=max_bulk_upload %}
{% include "partials/upload_form.html" %}
{% endwith %}
</div>
</section>
{% endif %}
+25 -18
View File
@@ -32,29 +32,36 @@
<span class="stat-card__label">на файл</span>
</div>
</div>
{% if quota %}
<div class="container quota-bar-wrap">
<div class="quota-bar">
<div class="quota-bar__header">
<span>Квота: {{ quota.group.name if quota.group else 'Пользователи' }}</span>
<span>
{{ format_size(quota.used) }}
{% if not quota.unlimited %}
/ {{ quota.group.disk_quota_mb }} МБ
{% else %}
/ без лимита
{% endif %}
</span>
</div>
{% if not quota.unlimited %}
<div class="quota-bar__track">
<div class="quota-bar__fill {% if quota.percent > 90 %}quota-bar__fill--warn{% endif %}" style="width: {{ quota.percent }}%"></div>
</div>
{% endif %}
</div>
</div>
{% endif %}
</section>
<section id="upload" class="upload-section">
<div class="container">
<h2 class="section-title">Загрузить фото</h2>
<form action="{{ url_for('main.upload') }}" method="post" enctype="multipart/form-data" class="upload-form" id="uploadForm">
<div class="dropzone" id="dropzone">
<input type="file" name="photo" id="photoInput" accept="image/png,image/jpeg,image/gif,image/webp,image/bmp" hidden>
<div class="dropzone__icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 16V4m0 0L8 8m4-4l4 4"/>
<path d="M20 16.5v1a2.5 2.5 0 01-2.5 2.5h-11A2.5 2.5 0 014 17.5v-1"/>
</svg>
</div>
<p class="dropzone__title">Перетащите фото сюда</p>
<p class="dropzone__hint">или нажмите для выбора файла</p>
<div class="dropzone__preview" id="preview" hidden>
<img id="previewImg" alt="Предпросмотр">
<span id="previewName"></span>
</div>
</div>
<button type="submit" class="btn btn--primary" id="submitBtn" disabled>Загрузить</button>
</form>
{% with folder_id=None, max_bulk_upload=max_bulk_upload %}
{% include "partials/upload_form.html" %}
{% endwith %}
</div>
</section>
+3 -24
View File
@@ -46,30 +46,9 @@
<section id="upload" class="upload-section">
<div class="container">
<h2 class="section-title">Загрузить фото</h2>
<form action="{{ url_for('main.upload') }}" method="post" enctype="multipart/form-data" class="upload-form" id="uploadForm">
<div class="dropzone" id="dropzone">
<input type="file" name="photo" id="photoInput" accept="image/png,image/jpeg,image/gif,image/webp,image/bmp" hidden>
<div class="dropzone__icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 16V4m0 0L8 8m4-4l4 4"/>
<path d="M20 16.5v1a2.5 2.5 0 01-2.5 2.5h-11A2.5 2.5 0 014 17.5v-1"/>
</svg>
</div>
<p class="dropzone__title">Перетащите фото сюда</p>
<p class="dropzone__hint">или нажмите для выбора файла</p>
<p class="dropzone__formats">PNG · JPG · GIF · WEBP · BMP</p>
<div class="dropzone__preview" id="preview" hidden>
<img id="previewImg" alt="Предпросмотр">
<span id="previewName"></span>
</div>
</div>
<button type="submit" class="btn btn--primary" id="submitBtn" disabled>
<span>Загрузить на сервер</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</button>
</form>
{% with folder_id=None, max_bulk_upload=max_bulk_upload %}
{% include "partials/upload_form.html" %}
{% endwith %}
</div>
</section>
{% endif %}
+22
View File
@@ -0,0 +1,22 @@
<form action="{{ url_for('main.upload') }}" method="post" enctype="multipart/form-data" class="upload-form" id="uploadForm">
{% if folder_id %}<input type="hidden" name="folder_id" value="{{ folder_id }}">{% endif %}
<div class="dropzone" id="dropzone">
<input type="file" name="photos" id="photoInput" accept="image/png,image/jpeg,image/gif,image/webp,image/bmp" multiple data-max="{{ max_bulk_upload|default(100) }}" hidden>
<div class="dropzone__icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 16V4m0 0L8 8m4-4l4 4"/>
<path d="M20 16.5v1a2.5 2.5 0 01-2.5 2.5h-11A2.5 2.5 0 014 17.5v-1"/>
</svg>
</div>
<p class="dropzone__title">Перетащите фото сюда</p>
<p class="dropzone__hint">или выберите до {{ max_bulk_upload|default(100) }} файлов</p>
<p class="dropzone__formats">PNG · JPG · GIF · WEBP · BMP</p>
<div class="dropzone__preview" id="preview" hidden>
<img id="previewImg" alt="Предпросмотр">
<span id="previewName"></span>
</div>
</div>
<button type="submit" class="btn btn--primary" id="submitBtn" disabled>
<span>Загрузить</span>
</button>
</form>
+3 -12
View File
@@ -21,18 +21,9 @@
<section id="upload" class="upload-section">
<div class="container">
<h2 class="section-title">Загрузить в папку</h2>
<form action="{{ url_for('main.upload') }}" method="post" enctype="multipart/form-data" class="upload-form" id="uploadForm">
<input type="hidden" name="folder_id" value="{{ folder.id }}">
<div class="dropzone" id="dropzone">
<input type="file" name="photo" id="photoInput" accept="image/png,image/jpeg,image/gif,image/webp,image/bmp" hidden>
<p class="dropzone__title">Перетащите фото сюда</p>
<div class="dropzone__preview" id="preview" hidden>
<img id="previewImg" alt="Предпросмотр">
<span id="previewName"></span>
</div>
</div>
<button type="submit" class="btn btn--primary" id="submitBtn" disabled>Загрузить</button>
</form>
{% with folder_id=folder.id, max_bulk_upload=max_bulk_upload %}
{% include "partials/upload_form.html" %}
{% endwith %}
</div>
</section>
{% endif %}