Files
evo-sync/web/templates/admin/user_detail.html
mguschin 5ead89e0cf feat: Evotor user lifecycle, RBAC, admin panel
- Receive Evotor webhooks: POST /user/create, /user/verify, /user/token
- Create users in pending status; match to existing users by email/phone
- Send invite link via Celery notification task; user sets password at /invite
- Abstract EmailProvider/SMSProvider with ConsoleEmailProvider default
- Role-based access control: role enum on users + roles/permissions tables
- Admin panel: /admin/users (list, filter, search, paginate), user detail card
  with activate/suspend/reset-password/send-invite/edit/delete actions
- Admin roles management: /admin/roles with per-role permission assignment
- Extend user profile card: role, status, Evotor ID, email confirmation badge
- Auth routes: register, login, logout, confirm-email, forgot/reset password
- Alembic migrations 0002 (full schema + new fields) and 0003 (RBAC + seeds)
- Port Pico CSS + Bootstrap Icons UI from Node.js commit (854c912)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:01:36 +03:00

148 lines
8.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}{{ target.first_name }} {{ target.last_name }} — Админ — ЭВОСИНК{% endblock %}
{% block content %}
<nav class="breadcrumb">
<li class="breadcrumb-item"><a href="/admin/users">Пользователи</a></li>
<li class="breadcrumb-item active">{{ target.first_name }} {{ target.last_name }}</li>
</nav>
{% if request.query_params.get('success') == 'reset_sent' %}
<div class="alert alert-success mb-3"><p>Ссылка для сброса пароля отправлена.</p></div>
{% elif request.query_params.get('success') == 'invite_sent' %}
<div class="alert alert-success mb-3"><p>Приглашение отправлено.</p></div>
{% elif request.query_params.get('success') == 'saved' %}
<div class="alert alert-success mb-3"><p>Данные сохранены.</p></div>
{% endif %}
<div class="row gap-3 align-start">
<div class="col-lg-6">
<article class="card">
<header><h2>Профиль</h2></header>
<ul class="list-group">
<li class="list-group-item"><span class="text-muted small">ID</span><span>{{ target.id }}</span></li>
<li class="list-group-item"><span class="text-muted small">Имя</span><span>{{ target.first_name }} {{ target.last_name }}</span></li>
<li class="list-group-item"><span class="text-muted small">Email</span>
<span>{{ target.email }}
{% if target.is_email_confirmed %}
<span class="badge badge-success ms-1">подтверждён</span>
{% else %}
<span class="badge badge-warning ms-1">не подтверждён</span>
{% endif %}
</span>
</li>
<li class="list-group-item"><span class="text-muted small">Телефон</span><span>{{ target.phone }}</span></li>
<li class="list-group-item"><span class="text-muted small">Роль</span>
<span>
{% if target.role == 'system' %}<span class="badge badge-danger">Системный</span>
{% elif target.role == 'admin' %}<span class="badge badge-warning">Администратор</span>
{% else %}<span class="badge badge-secondary">Пользователь</span>
{% endif %}
</span>
</li>
<li class="list-group-item"><span class="text-muted small">Статус</span>
<span>
{% if target.status == 'active' %}<span class="badge badge-success">Активен</span>
{% elif target.status == 'pending' %}<span class="badge badge-warning">Ожидает</span>
{% else %}<span class="badge badge-danger">Заблокирован</span>
{% endif %}
</span>
</li>
<li class="list-group-item"><span class="text-muted small">Регистрация</span><span>{{ target.created_at | datefmt }}</span></li>
{% if target.evotor_user_id %}
<li class="list-group-item"><span class="text-muted small">Эвотор ID</span><span class="font-monospace small">{{ target.evotor_user_id }}</span></li>
{% endif %}
{% if target.invite_token %}
<li class="list-group-item"><span class="text-muted small">Приглашение до</span><span>{{ target.invite_expires | datefmt }}</span></li>
{% endif %}
</ul>
</article>
{% if target.evotor_meta %}
<article class="card mt-3">
<header><h2>Данные Эвотор</h2></header>
<div class="card-body">
<pre class="font-monospace small" style="overflow-x:auto; white-space:pre-wrap; margin:0;">{{ target.evotor_meta | tojson(indent=2) }}</pre>
</div>
</article>
{% endif %}
</div>
<div class="col-lg-6">
<article class="card">
<header><h2>Действия</h2></header>
<div class="card-body d-grid gap-2">
{% if target.status != 'active' %}
<form method="post" action="/admin/users/{{ target.id }}/activate">
<button type="submit" class="w-100">
<i class="bi bi-check-circle me-1"></i>Активировать
</button>
</form>
{% endif %}
{% if target.status != 'suspended' %}
<form method="post" action="/admin/users/{{ target.id }}/suspend">
<button type="submit" class="w-100 outline danger">
<i class="bi bi-slash-circle me-1"></i>Заблокировать
</button>
</form>
{% endif %}
<form method="post" action="/admin/users/{{ target.id }}/reset-password">
<button type="submit" class="w-100 outline secondary">
<i class="bi bi-key me-1"></i>Сбросить пароль
</button>
</form>
<form method="post" action="/admin/users/{{ target.id }}/send-invite">
<button type="submit" class="w-100 outline secondary">
<i class="bi bi-envelope me-1"></i>Отправить приглашение
</button>
</form>
{% if user.role == 'system' and target.id != user.id %}
<form method="post" action="/admin/users/{{ target.id }}/delete"
onsubmit="return confirm('Удалить пользователя {{ target.email }}? Это действие необратимо.')">
<button type="submit" class="w-100 danger sm">
<i class="bi bi-trash me-1"></i>Удалить
</button>
</form>
{% endif %}
</div>
</article>
<article class="card mt-3">
<header><h2>Редактировать</h2></header>
<div class="card-body">
<form method="post" action="/admin/users/{{ target.id }}/edit">
<div class="row gap-2 mb-2">
<div class="col">
<label for="first_name">Имя
<input type="text" id="first_name" name="first_name" value="{{ target.first_name }}" required>
</label>
</div>
<div class="col">
<label for="last_name">Фамилия
<input type="text" id="last_name" name="last_name" value="{{ target.last_name }}" required>
</label>
</div>
</div>
<label for="email">Email
<input type="email" id="email" name="email" value="{{ target.email }}">
</label>
<label for="phone">Телефон
<input type="tel" id="phone" name="phone" value="{{ target.phone }}">
</label>
{% if user.role == 'system' %}
<label for="role">Роль
<select id="role" name="role">
<option value="user" {% if target.role == 'user' %}selected{% endif %}>Пользователь</option>
<option value="admin" {% if target.role == 'admin' %}selected{% endif %}>Администратор</option>
<option value="system" {% if target.role == 'system' %}selected{% endif %}>Системный</option>
</select>
</label>
{% endif %}
<button type="submit">Сохранить</button>
</form>
</div>
</article>
</div>
</div>
{% endblock %}