167 lines
8.0 KiB
HTML
167 lines
8.0 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ title }}{% endblock %}
|
|
|
|
{% block content %}
|
|
{% include '_quota_warning.html' %}
|
|
<div class="row">
|
|
<div class="col-md-8 offset-md-2">
|
|
|
|
{# ── Account Status card (edit mode only, not own account) ── #}
|
|
{% if user and user.id != current_user.id %}
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-body d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<span class="fw-semibold me-2">Account Status:</span>
|
|
<span class="badge fs-6 bg-{{ 'success' if user.active else 'secondary' }}">
|
|
{{ 'Active' if user.active else 'Disabled' }}
|
|
</span>
|
|
<div class="form-text mt-1">
|
|
{% if user.active %}
|
|
Disabling this account will immediately prevent the user from logging in.
|
|
{% else %}
|
|
This account is currently disabled — the user cannot log in.
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('auth.toggle_active', user_id=user.id) }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit"
|
|
class="btn btn-sm {{ 'btn-outline-secondary' if user.active else 'btn-outline-success' }}"
|
|
onclick="return confirm('{{ 'Disable' if user.active else 'Enable' }} user {{ user.username }}?')">
|
|
<i class="bi bi-{{ 'person-slash' if user.active else 'person-check' }} me-1"></i>
|
|
{{ 'Disable Account' if user.active else 'Enable Account' }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# ── Edit form ── #}
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-primary text-white">
|
|
<h4 class="mb-0">{{ title }}</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST">
|
|
{{ form.hidden_tag() }}
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
{{ form.username.label(class="form-label") }}
|
|
{{ form.username(class="form-control") }}
|
|
{% if form.username.errors %}
|
|
<div class="text-danger small mt-1">
|
|
{% for error in form.username.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="col-md-6 mb-3">
|
|
{{ form.full_name.label(class="form-label") }}
|
|
{{ form.full_name(class="form-control", placeholder="e.g. Jane Smith") }}
|
|
{% if form.full_name.errors %}
|
|
<div class="text-danger small mt-1">
|
|
{% for error in form.full_name.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
{{ form.email.label(class="form-label") }}
|
|
{{ form.email(class="form-control") }}
|
|
{% if form.email.errors %}
|
|
<div class="text-danger small mt-1">
|
|
{% for error in form.email.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{# MT-15 — an External Inspector is invited by email and chooses
|
|
their own password, so the admin never sets one. The JS at the
|
|
foot of this page swaps these two blocks when the role changes;
|
|
the server decides independently of the JS. #}
|
|
<div id="inviteNotice" class="alert alert-info d-none">
|
|
<i class="bi bi-envelope me-1"></i>
|
|
<strong>This account will be invited by email.</strong>
|
|
External inspectors work outside the business, so we do not set
|
|
a password for them. On save, an invitation is sent to the email
|
|
address above with a link to choose their own password. The link
|
|
is valid for 72 hours.
|
|
</div>
|
|
|
|
<div class="row" id="passwordFields">
|
|
<div class="col-md-6 mb-3">
|
|
{{ form.password.label(class="form-label") }}
|
|
{{ form.password(class="form-control", placeholder="Leave blank to keep current" if user else "") }}
|
|
{% if form.password.errors %}
|
|
<div class="text-danger small mt-1">
|
|
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="col-md-6 mb-3">
|
|
{{ form.confirm_password.label(class="form-label") }}
|
|
{{ form.confirm_password(class="form-control") }}
|
|
{% if form.confirm_password.errors %}
|
|
<div class="text-danger small mt-1">
|
|
{% for error in form.confirm_password.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
{{ form.role.label(class="form-label") }}
|
|
{% if director_editing %}
|
|
{# Directors can see the current role but cannot change it #}
|
|
<div class="form-control bg-light text-muted" style="cursor: not-allowed;">
|
|
{{ (user.role if user else 'Inspector').replace('_', ' ')|title }}
|
|
</div>
|
|
<div class="form-text">Role assignment requires Administrator access.</div>
|
|
{% else %}
|
|
{{ form.role(class="form-select") }}
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-save"></i> Save User
|
|
</button>
|
|
<a href="{{ url_for('auth.list_users') }}" class="btn btn-secondary">
|
|
<i class="bi bi-x-circle"></i> Cancel
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
'use strict';
|
|
var roleSel = document.getElementById('role');
|
|
var pwBlock = document.getElementById('passwordFields');
|
|
var notice = document.getElementById('inviteNotice');
|
|
if (!roleSel || !pwBlock || !notice) return; // director view has no role select
|
|
|
|
function sync() {
|
|
var invited = roleSel.value === 'external_inspector';
|
|
pwBlock.classList.toggle('d-none', invited);
|
|
notice.classList.toggle('d-none', !invited);
|
|
// Clear anything already typed so an invited account can never be created
|
|
// with an admin-chosen password sitting in the POST body.
|
|
if (invited) {
|
|
pwBlock.querySelectorAll('input').forEach(function (i) { i.value = ''; });
|
|
}
|
|
}
|
|
roleSel.addEventListener('change', sync);
|
|
sync();
|
|
})();
|
|
</script>
|
|
{% endblock %} |