981a481e3f
Админ-страница «Ссылки на установку пароля» (Сотрудники → действия): таблица всех активных сотрудников со статусом (пароль установлен / MM привязан / активная ссылка), кнопки «создать всем без пароля», создать/ обновить/отозвать по одному, экспорт CSV. Ссылки одноразовые, TTL 30 дней. При активации по ссылке, помимо пароля портала, теперь синхронно ставится тот же пароль в Mattermost: аккаунт связывается по email/ID или создаётся (первичная связь). Ошибка MM не отменяет установку пароля портала. - services/password_invite: bulk_create_invites_for_pending, active_invites_by_employee, revoke_active_invites (BULK_TTL_DAYS=30) - mattermost sync_service: link_and_set_password (ok|disabled|error) - views_password: MM-синк после commit (вне транзакции, best-effort) - EmployeeAdmin: action password-invites + шаблон - tests: 6 новых (bulk, revoke, MM-линк ok/disabled, активация, admin+CSV) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
6.1 KiB
HTML
82 lines
6.1 KiB
HTML
{% extends "admin/base_site.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block content %}
|
|
<div style="margin-bottom:1rem;">
|
|
<p style="color:var(--color-base-500);font-size:0.85rem;max-width:60rem;">
|
|
{% blocktrans %}Одноразовые ссылки первичной установки пароля. По ссылке сотрудник задаёт пароль портала; тот же пароль ставится в Mattermost (аккаунт связывается по email или создаётся). Ссылка действует {{ ttl_days }} дней и сгорает после использования.{% endblocktrans %}
|
|
</p>
|
|
</div>
|
|
|
|
<div style="display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap;margin-bottom:1rem;">
|
|
<form method="post" style="margin:0;">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="generate_all">
|
|
<button type="submit" class="button" style="background:var(--color-primary-600);color:#fff;padding:0.4rem 0.9rem;border-radius:0.375rem;border:none;cursor:pointer;font-size:0.85rem;">
|
|
{% blocktrans %}Создать ссылки всем без пароля ({{ pending_count }}){% endblocktrans %}
|
|
</button>
|
|
</form>
|
|
<a href="?export=csv" class="button" style="padding:0.4rem 0.9rem;border-radius:0.375rem;border:1px solid var(--color-base-300);text-decoration:none;font-size:0.85rem;color:var(--color-base-700);">
|
|
{% trans "Скачать CSV" %} ({{ with_link_count }})
|
|
</a>
|
|
</div>
|
|
|
|
<div style="overflow-x:auto;border:1px solid var(--color-base-200);border-radius:0.5rem;">
|
|
<table style="border-collapse:collapse;width:100%;font-size:0.85rem;">
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align:left;padding:0.5rem 0.75rem;border-bottom:1px solid var(--color-base-200);">{% trans "Сотрудник" %}</th>
|
|
<th style="text-align:left;padding:0.5rem 0.75rem;border-bottom:1px solid var(--color-base-200);">{% trans "Логин" %}</th>
|
|
<th style="text-align:center;padding:0.5rem 0.5rem;border-bottom:1px solid var(--color-base-200);">{% trans "Пароль" %}</th>
|
|
<th style="text-align:center;padding:0.5rem 0.5rem;border-bottom:1px solid var(--color-base-200);">Mattermost</th>
|
|
<th style="text-align:left;padding:0.5rem 0.75rem;border-bottom:1px solid var(--color-base-200);">{% trans "Ссылка" %}</th>
|
|
<th style="text-align:right;padding:0.5rem 0.75rem;border-bottom:1px solid var(--color-base-200);">{% trans "Действия" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in rows %}
|
|
<tr>
|
|
<td style="padding:0.4rem 0.75rem;border-bottom:1px solid var(--color-base-100);white-space:nowrap;">{{ row.employee.get_full_name|default:row.employee.username }}</td>
|
|
<td style="padding:0.4rem 0.75rem;border-bottom:1px solid var(--color-base-100);white-space:nowrap;color:var(--color-base-500);">{{ row.employee.username }}</td>
|
|
<td style="text-align:center;padding:0.4rem 0.5rem;border-bottom:1px solid var(--color-base-100);">
|
|
{% if row.password_set %}<span title="{% trans 'Пароль установлен' %}" style="color:var(--color-green-600);">✔</span>{% else %}<span title="{% trans 'Пароль не установлен' %}" style="color:var(--color-base-400);">—</span>{% endif %}
|
|
</td>
|
|
<td style="text-align:center;padding:0.4rem 0.5rem;border-bottom:1px solid var(--color-base-100);">
|
|
{% if row.mm_linked %}<span title="{% trans 'Аккаунт связан' %}" style="color:var(--color-green-600);">✔</span>{% else %}<span title="{% trans 'Не связан' %}" style="color:var(--color-base-400);">—</span>{% endif %}
|
|
</td>
|
|
<td style="padding:0.4rem 0.75rem;border-bottom:1px solid var(--color-base-100);">
|
|
{% if row.link %}
|
|
<div style="display:flex;gap:0.35rem;align-items:center;">
|
|
<input type="text" readonly value="{{ row.link }}" onclick="this.select()" style="width:22rem;max-width:40vw;font-size:0.75rem;padding:0.2rem 0.4rem;border:1px solid var(--color-base-200);border-radius:0.25rem;background:var(--color-base-50);">
|
|
<button type="button" onclick="navigator.clipboard.writeText('{{ row.link }}');this.textContent='✓'" title="{% trans 'Копировать' %}" style="cursor:pointer;border:1px solid var(--color-base-300);background:transparent;border-radius:0.25rem;padding:0.2rem 0.45rem;font-size:0.75rem;">⧉</button>
|
|
{% if row.expires_at %}<span style="color:var(--color-base-400);font-size:0.7rem;white-space:nowrap;">{% trans "до" %} {{ row.expires_at|date:"d.m.Y" }}</span>{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<span style="color:var(--color-base-400);">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td style="text-align:right;padding:0.4rem 0.75rem;border-bottom:1px solid var(--color-base-100);white-space:nowrap;">
|
|
<form method="post" style="display:inline;margin:0;">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="generate_one">
|
|
<input type="hidden" name="employee_id" value="{{ row.employee.pk }}">
|
|
<button type="submit" style="cursor:pointer;border:1px solid var(--color-base-300);background:transparent;border-radius:0.25rem;padding:0.2rem 0.5rem;font-size:0.75rem;">{% if row.link %}{% trans "Обновить" %}{% else %}{% trans "Создать" %}{% endif %}</button>
|
|
</form>
|
|
{% if row.link %}
|
|
<form method="post" style="display:inline;margin:0;">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="revoke_one">
|
|
<input type="hidden" name="employee_id" value="{{ row.employee.pk }}">
|
|
<button type="submit" style="cursor:pointer;border:1px solid var(--color-red-300);color:var(--color-red-600);background:transparent;border-radius:0.25rem;padding:0.2rem 0.5rem;font-size:0.75rem;">{% trans "Отозвать" %}</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="6" style="padding:1rem;text-align:center;color:var(--color-base-400);">{% trans "Активных сотрудников нет." %}</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endblock %}
|