Первый коммит: загрузка проекта SecretText
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="mb-0">Панель администратора</h2>
|
||||
<div>
|
||||
<span class="text-muted me-2">{{ session.username }}</span>
|
||||
<a href="{{ url_for('admin_logout') }}" class="btn btn-sm btn-outline-secondary">Выйти</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-uppercase text-muted small">Всего секретов</div>
|
||||
<div class="h5 mb-0">{{ stats.total }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-uppercase text-muted small">Просмотрено</div>
|
||||
<div class="h5 mb-0">{{ stats.viewed }} ({{ stats.viewed_percent }}%)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-uppercase text-muted small">Ожидают просмотра</div>
|
||||
<div class="h5 mb-0">{{ stats.not_viewed }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-uppercase text-muted small">Заблокированные IP</div>
|
||||
<div class="h5 mb-0">{{ stats.blocked_ips }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs mb-4" id="adminTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="secrets-tab" data-bs-toggle="tab" data-bs-target="#secrets"
|
||||
type="button" role="tab" aria-controls="secrets" aria-selected="true">
|
||||
Секреты <span class="badge bg-secondary">{{ logs|length }}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="users-tab" data-bs-toggle="tab" data-bs-target="#users" type="button"
|
||||
role="tab" aria-controls="users" aria-selected="false">
|
||||
Пользователи <span class="badge bg-secondary">{{ users|length }}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="security-tab" data-bs-toggle="tab" data-bs-target="#security" type="button"
|
||||
role="tab" aria-controls="security" aria-selected="false">
|
||||
Безопасность
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="blocked-tab" data-bs-toggle="tab" data-bs-target="#blocked" type="button"
|
||||
role="tab" aria-controls="blocked" aria-selected="false">
|
||||
Заблокированные <span class="badge bg-secondary">{{ blocked_ips|length }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="secrets" role="tabpanel" aria-labelledby="secrets-tab">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Фильтр:</span>
|
||||
<div>
|
||||
<a href="{{ url_for('admin_panel') }}?filter=all"
|
||||
class="btn btn-sm btn-outline-secondary {% if filter == 'all' or not filter %}active{% endif %}">Все</a>
|
||||
<a href="{{ url_for('admin_panel') }}?filter=pending"
|
||||
class="btn btn-sm btn-outline-secondary {% if filter == 'pending' %}active{% endif %}">Ожидают</a>
|
||||
<a href="{{ url_for('admin_panel') }}?filter=viewed"
|
||||
class="btn btn-sm btn-outline-secondary {% if filter == 'viewed' %}active{% endif %}">Просмотрены</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Создан</th>
|
||||
<th>IP создания</th>
|
||||
<th>Пользователь</th>
|
||||
<th>Просмотрен</th>
|
||||
<th>IP просмотра</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td><code class="bg-light p-1 rounded">{{ log.secret_id[:12] }}...</code></td>
|
||||
<td>{{ log.created_at|format_datetime if log.created_at else '-' }}</td>
|
||||
<td><code>{{ log.created_ip or '-' }}</code></td>
|
||||
<td>
|
||||
{% if log.user %}
|
||||
<span class="badge bg-secondary">{{ log.user.username }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Аноним</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ log.viewed_at|format_datetime if log.viewed_at else '-' }}</td>
|
||||
<td><code>{{ log.viewed_ip or '-' }}</code></td>
|
||||
<td>
|
||||
{% if log.is_viewed %}
|
||||
<span class="badge bg-secondary">Просмотрен</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Ожидает</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4 text-muted">Нет записей</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if logs|length >= per_page %}
|
||||
<div class="p-3">
|
||||
<nav>
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
<li class="page-item {% if page <= 1 %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('admin_panel', page=page-1, filter=filter) if page > 1 else '#' }}">Назад</a>
|
||||
</li>
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ page }}</span>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('admin_panel', page=page+1, filter=filter) }}">Вперед</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="users" role="tabpanel" aria-labelledby="users-tab">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>Управление пользователями</span>
|
||||
<a href="{{ url_for('admin_create_user') }}" class="btn btn-sm btn-secondary">Создать
|
||||
пользователя</a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Имя пользователя</th>
|
||||
<th>Роль</th>
|
||||
<th>Создан</th>
|
||||
<th>Последний вход</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td><strong>{{ user.username }}</strong></td>
|
||||
<td>
|
||||
<form action="{{ url_for('admin_change_role', user_id=user.id) }}" method="POST"
|
||||
class="d-flex">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<select name="role" class="form-select form-select-sm me-1"
|
||||
style="width: auto;">
|
||||
<option value="user" {% if user.role==
|
||||
'user' %}selected{% endif %}>user</option>
|
||||
<option value="admin" {% if user.role==
|
||||
'admin' %}selected{% endif %}>admin</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>{{ user.created_at|format_datetime if user.created_at else '-' }}</td>
|
||||
<td>{{ user.last_login|format_datetime if user.last_login else 'Никогда' }}</td>
|
||||
<td>
|
||||
{% if user.is_active %}
|
||||
<span class="badge bg-secondary">Активен</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Деактивирован</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('admin_toggle_user', user_id=user.id) }}"
|
||||
class="btn btn-outline-secondary">
|
||||
{% if user.is_active %}Деактивировать{% else %}Активировать{% endif %}
|
||||
</a>
|
||||
<a href="{{ url_for('admin_delete_user', user_id=user.id) }}"
|
||||
class="btn btn-outline-secondary"
|
||||
onclick="return confirm('Удалить пользователя?')">Удалить</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4 text-muted">Нет пользователей</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">Сменить пароль</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('admin_change_password') }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="old_password" class="form-label small">Текущий пароль</label>
|
||||
<input type="password" name="old_password" id="old_password"
|
||||
class="form-control form-control-sm" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label small">Новый пароль</label>
|
||||
<input type="password" name="new_password" id="new_password"
|
||||
class="form-control form-control-sm" required minlength="6">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label small">Подтвердите пароль</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password"
|
||||
class="form-control form-control-sm" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm btn-secondary">Сменить пароль</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="security" role="tabpanel" aria-labelledby="security-tab">
|
||||
<div class="card">
|
||||
<div class="card-header">Журнал событий безопасности</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Время</th>
|
||||
<th>IP</th>
|
||||
<th>Тип события</th>
|
||||
<th>Детали</th>
|
||||
<th>Пользователь</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in security_logs %}
|
||||
<tr>
|
||||
<td>{{ log.timestamp|format_datetime_seconds if log.timestamp else '-' }}</td>
|
||||
<td><code>{{ log.ip }}</code></td>
|
||||
<td><span class="badge bg-secondary">{{ log.event_type }}</span></td>
|
||||
<td>{{ log.details or '-' }}</td>
|
||||
<td>
|
||||
{% if log.user %}
|
||||
<span class="badge bg-secondary">{{ log.user.username }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Аноним</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-muted">Нет событий безопасности</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="blocked" role="tabpanel" aria-labelledby="blocked-tab">
|
||||
<div class="card">
|
||||
<div class="card-header">Заблокированные IP-адреса</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>IP адрес</th>
|
||||
<th>Тип блокировки</th>
|
||||
<th>Осталось времени</th>
|
||||
<th>Количество блокировок</th>
|
||||
<th>Действие</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ip in blocked_ips %}
|
||||
<tr>
|
||||
<td><code>{{ ip.ip }}</code></td>
|
||||
<td>
|
||||
{% if ip.permanent %}
|
||||
<span class="badge bg-secondary">Постоянная</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Временная</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if ip.permanent %}
|
||||
—
|
||||
{% else %}
|
||||
{{ ip.remaining_minutes }} мин.
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ ip.block_count }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('admin_unblock', ip=ip.ip) }}"
|
||||
class="btn btn-outline-secondary">Разблокировать</a>
|
||||
{% if not ip.permanent %}
|
||||
<a href="{{ url_for('admin_block', ip=ip.ip) }}"
|
||||
class="btn btn-outline-secondary"
|
||||
onclick="return confirm('Заблокировать IP постоянно?')">Заблокировать</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-muted">Нет заблокированных IP-адресов</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user