Files
IT_Ticket_System/app/templates/admin/edit_user.html
T

200 lines
8.4 KiB
HTML

{% extends "base.html" %}
{% block title %}Edit User{% endblock %}
{% block page_title %}Edit User{% endblock %}
{% block head %}
<style>
/* ── Password rule checklist ── */
.pw-rules{
margin-top:8px;display:grid;grid-template-columns:1fr 1fr;gap:3px 12px;
background:var(--bg);border:1px solid var(--border);border-radius:8px;
padding:10px 12px;
}
.pw-rule{
display:flex;align-items:center;gap:5px;
font-size:11.5px;color:var(--muted);
transition:color .2s;
}
.pw-rule .ri{
font-size:13px;width:14px;text-align:center;
transition:color .2s, transform .15s;
}
.pw-rule.met{color:var(--success);}
.pw-rule.met .ri{color:var(--success);transform:scale(1.1);}
.pw-rule.unmet .ri{color:var(--border2);}
</style>
{% 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>
<input type="password" class="form-control" id="new-password" name="new_password" placeholder="Leave blank to keep current"/>
</div>
<div class="col-md-6">
<label class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="confirm-password" name="confirm_password" placeholder="Repeat new password"/>
</div>
<div class="col-12">
<!-- Password strength bar -->
<div style="height:4px;background:var(--border);border-radius:2px;margin-top:4px;" id="pw-strength-track">
<div id="pw-strength-bar" style="height:100%;border-radius:2px;width:0%;transition:width .3s,background .3s;"></div>
</div>
<div id="pw-strength-label" style="font-size:11px;color:var(--muted);margin-top:4px;"></div>
<!-- Rule checklist — mirrors server-side validate_password() rules -->
<div class="pw-rules" id="pw-rules-box" style="display:none;">
<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>
// ── Password strength + rule checklist ────────────────────────────────────────
// Mirrors server-side validate_password() rules in validation_service.py.
// Any change to the server rules must be reflected here too.
const PW_RULES = {
len : pw => pw.length >= 8,
upper : pw => /[A-Z]/.test(pw),
digit : pw => /\d/.test(pw),
special: pw => /[!@#$%^&*()\-_=+\[\]{};:'",.<>?/\\|`~]/.test(pw),
};
function setPwRule(id, met) {
const el = document.getElementById('pw-rule-' + id);
if (!el) return;
el.className = 'pw-rule ' + (met ? 'met' : 'unmet');
el.querySelector('.ri').innerHTML = met
? '<i class="bi bi-check-circle-fill"></i>'
: '<i class="bi bi-circle"></i>';
}
document.getElementById('new-password').addEventListener('input', function () {
const val = this.value;
const bar = document.getElementById('pw-strength-bar');
const label = document.getElementById('pw-strength-label');
const rules = document.getElementById('pw-rules-box');
if (val.length === 0) {
bar.style.width = '0%';
label.textContent = '';
rules.style.display = 'none';
return;
}
rules.style.display = 'grid';
setPwRule('len', PW_RULES.len(val));
setPwRule('upper', PW_RULES.upper(val));
setPwRule('digit', PW_RULES.digit(val));
setPwRule('special', PW_RULES.special(val));
let score = 0;
if (PW_RULES.len(val)) score++;
if (PW_RULES.upper(val)) score++;
if (PW_RULES.digit(val)) score++;
if (PW_RULES.special(val)) score++;
if (val.length >= 16) score++; // bonus for extra length
const levels = [
{ pct: '20%', bg: '#dc2626', text: 'Very weak' },
{ pct: '40%', bg: '#d97706', text: 'Weak' },
{ pct: '60%', bg: '#ca8a04', text: 'Fair' },
{ pct: '80%', bg: '#16a34a', text: 'Strong' },
{ pct: '100%', bg: '#059669', text: 'Very strong' },
];
const lvl = levels[Math.min(score - 1, 4)] || levels[0];
bar.style.width = lvl.pct;
bar.style.background = lvl.bg;
label.textContent = lvl.text;
label.style.color = lvl.bg;
});
// ── Client-side password match check before submit ────────────────────────────
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();
document.getElementById('confirm-password').style.borderColor = 'var(--danger)';
const msg = document.createElement('div');
msg.style.cssText = 'font-size:12px;color:var(--danger);margin-top:4px;';
msg.textContent = 'Passwords do not match.';
const existing = document.getElementById('pw-match-msg');
if (existing) existing.remove();
msg.id = 'pw-match-msg';
document.getElementById('confirm-password').insertAdjacentElement('afterend', msg);
}
});
document.getElementById('confirm-password').addEventListener('input', function () {
this.style.borderColor = '';
const msg = document.getElementById('pw-match-msg');
if (msg) msg.remove();
});
</script>
{% endblock %}