145 lines
6.7 KiB
HTML
145 lines
6.7 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Edit User{% endblock %}
|
|
{% block page_title %}Edit User{% endblock %}
|
|
|
|
{% block head %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/password-strength.css') }}"/>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-6">
|
|
<div class="mb-3"><a href="{{ url_for('admin.users') }}" style="font-size:13px;color:var(--accent3);">← Back to Users</a></div>
|
|
<div class="card">
|
|
<div class="card-header"><i class="bi bi-person-gear me-2"></i>Edit: {{ user.full_name }}</div>
|
|
<div class="card-body">
|
|
<form method="POST">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Full Name</label>
|
|
<input type="text" class="form-control" name="full_name" value="{{ user.full_name }}" required/>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Department</label>
|
|
<input type="text" class="form-control" name="department" value="{{ user.department or '' }}"/>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Phone</label>
|
|
<input type="text" class="form-control" name="phone" value="{{ user.phone or '' }}"/>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Role</label>
|
|
<select class="form-select" name="role">
|
|
{% for r in roles %}
|
|
<option value="{{ r }}" {% if r == user.role %}selected{% endif %}>{{ r.replace('_',' ').title() }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
|
<input type="checkbox" name="is_active" {% if user.is_active %}checked{% endif %}
|
|
style="accent-color:var(--success);width:16px;height:16px;"/>
|
|
<span style="font-size:14px;">Account Active</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-12"><hr style="border-color:var(--border);"/></div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">New Password <span style="color:var(--muted);font-weight:400;">(optional)</span></label>
|
|
<div class="pw-wrap">
|
|
<input type="password" class="form-control pw-input" id="new-password" name="new_password"
|
|
maxlength="128" placeholder="Leave blank to keep current"
|
|
autocomplete="new-password"/>
|
|
<button type="button" class="pw-toggle" id="new-password-toggle"
|
|
title="Show / hide password" tabindex="-1">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
</div>
|
|
<div style="font-size:11px;color:var(--muted);margin-top:5px;">Leave both fields blank to keep the current password.</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Confirm Password</label>
|
|
<div class="pw-wrap">
|
|
<input type="password" class="form-control pw-input" id="confirm-password" name="confirm_password"
|
|
maxlength="128" placeholder="Repeat new password"
|
|
autocomplete="new-password"/>
|
|
<button type="button" class="pw-toggle" id="confirm-password-toggle"
|
|
title="Show / hide password" tabindex="-1">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
</div>
|
|
<div class="pw-match" id="pw-match-msg" aria-live="polite" aria-atomic="true"></div>
|
|
</div>
|
|
<div class="col-12">
|
|
<!-- Strength bar -->
|
|
<div class="pw-strength-wrap" id="pw-strength-wrap" style="display:none;">
|
|
<div class="pw-strength-track">
|
|
<div class="pw-strength-fill" id="pw-strength-bar"></div>
|
|
</div>
|
|
<div class="pw-strength-label" id="pw-strength-label"></div>
|
|
</div>
|
|
|
|
<!-- Rule checklist — mirrors server-side validate_password() rules -->
|
|
<div class="pw-rules" id="pw-rules-box" style="display:none;" aria-live="polite" aria-atomic="true">
|
|
<div class="pw-rule unmet" id="pw-rule-len">
|
|
<span class="ri"><i class="bi bi-circle"></i></span>
|
|
<span>8+ characters</span>
|
|
</div>
|
|
<div class="pw-rule unmet" id="pw-rule-upper">
|
|
<span class="ri"><i class="bi bi-circle"></i></span>
|
|
<span>Uppercase letter</span>
|
|
</div>
|
|
<div class="pw-rule unmet" id="pw-rule-digit">
|
|
<span class="ri"><i class="bi bi-circle"></i></span>
|
|
<span>Number</span>
|
|
</div>
|
|
<div class="pw-rule unmet" id="pw-rule-special">
|
|
<span class="ri"><i class="bi bi-circle"></i></span>
|
|
<span>Special character</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2 mt-4">
|
|
<button type="submit" class="btn btn-primary"><i class="bi bi-check2 me-2"></i>Save</button>
|
|
<a href="{{ url_for('admin.users') }}" class="btn btn-secondary">Cancel</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="{{ url_for('static', filename='js/password-strength.js') }}"></script>
|
|
<script>
|
|
const pwStrength = PasswordStrength.init({
|
|
password : 'new-password',
|
|
confirm : 'confirm-password',
|
|
strengthBar : 'pw-strength-bar',
|
|
strengthLabel: 'pw-strength-label',
|
|
strengthWrap : 'pw-strength-wrap',
|
|
rulesWrap : 'pw-rules-box',
|
|
rules: { len: 'pw-rule-len', upper: 'pw-rule-upper', digit: 'pw-rule-digit', special: 'pw-rule-special' },
|
|
matchMsg : 'pw-match-msg',
|
|
toggles: [
|
|
{ input: 'new-password', button: 'new-password-toggle' },
|
|
{ input: 'confirm-password', button: 'confirm-password-toggle' },
|
|
],
|
|
});
|
|
|
|
// ── Block submit on a real mismatch (server re-validates regardless) ──────────
|
|
document.querySelector('form').addEventListener('submit', function (e) {
|
|
const pw = document.getElementById('new-password').value;
|
|
const pw2 = document.getElementById('confirm-password').value;
|
|
if (pw !== pw2) {
|
|
e.preventDefault();
|
|
pwStrength.renderMatch();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|