Jul 2nd - Optimized code
This commit is contained in:
@@ -3,26 +3,7 @@
|
||||
{% block page_title %}User Management{% 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>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/password-strength.css') }}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -120,36 +101,39 @@
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Password *</label>
|
||||
<div style="position:relative;">
|
||||
<input type="password" class="form-control" name="password"
|
||||
id="pw-field" required minlength="8"
|
||||
<div class="pw-wrap">
|
||||
<input type="password" class="form-control pw-input" name="password"
|
||||
id="pw-field" required minlength="8" maxlength="128"
|
||||
placeholder="Min. 8 characters"
|
||||
style="padding-right:44px;"/>
|
||||
<button type="button" onclick="togglePw('pw-field','eye1')"
|
||||
style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--muted);cursor:pointer;padding:4px;">
|
||||
<i class="bi bi-eye" id="eye1"></i>
|
||||
autocomplete="new-password"/>
|
||||
<button type="button" class="pw-toggle" id="pw-field-toggle"
|
||||
title="Show / hide password" tabindex="-1">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Confirm Password *</label>
|
||||
<div style="position:relative;">
|
||||
<input type="password" class="form-control" name="confirm_password"
|
||||
id="pw-field2" required minlength="8"
|
||||
<div class="pw-wrap">
|
||||
<input type="password" class="form-control pw-input" name="confirm_password"
|
||||
id="pw-field2" required minlength="8" maxlength="128"
|
||||
placeholder="Repeat password"
|
||||
style="padding-right:44px;"/>
|
||||
<button type="button" onclick="togglePw('pw-field2','eye2')"
|
||||
style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--muted);cursor:pointer;padding:4px;">
|
||||
<i class="bi bi-eye" id="eye2"></i>
|
||||
autocomplete="new-password"/>
|
||||
<button type="button" class="pw-toggle" id="pw-field2-toggle"
|
||||
title="Show / hide password" tabindex="-1">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pw-match" id="pw-match-msg"></div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<!-- Password strength bar -->
|
||||
<div style="height:4px;background:var(--border);border-radius:2px;margin-top:4px;">
|
||||
<div id="pw-strength-bar" style="height:100%;border-radius:2px;width:0%;transition:width .3s,background .3s;"></div>
|
||||
<!-- 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>
|
||||
<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;">
|
||||
@@ -189,100 +173,31 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/password-strength.js') }}"></script>
|
||||
<script>
|
||||
// ── Show/hide password ────────────────────────────────────────────────────────
|
||||
function togglePw(fieldId, iconId) {
|
||||
const field = document.getElementById(fieldId);
|
||||
const icon = document.getElementById(iconId);
|
||||
if (field.type === 'password') {
|
||||
field.type = 'text';
|
||||
icon.className = 'bi bi-eye-slash';
|
||||
} else {
|
||||
field.type = 'password';
|
||||
icon.className = 'bi bi-eye';
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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('pw-field').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;
|
||||
const pwStrength = PasswordStrength.init({
|
||||
password : 'pw-field',
|
||||
confirm : 'pw-field2',
|
||||
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: 'pw-field', button: 'pw-field-toggle' },
|
||||
{ input: 'pw-field2', button: 'pw-field2-toggle' },
|
||||
],
|
||||
});
|
||||
|
||||
// ── Client-side password match check before submit ────────────────────────────
|
||||
// ── Block submit on a real mismatch (server re-validates regardless) ──────────
|
||||
document.querySelector('form').addEventListener('submit', function (e) {
|
||||
const pw = document.getElementById('pw-field').value;
|
||||
const pw2 = document.getElementById('pw-field2').value;
|
||||
if (pw !== pw2) {
|
||||
e.preventDefault();
|
||||
document.getElementById('pw-field2').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('pw-field2').insertAdjacentElement('afterend', msg);
|
||||
pwStrength.renderMatch();
|
||||
}
|
||||
});
|
||||
document.getElementById('pw-field2').addEventListener('input', function () {
|
||||
this.style.borderColor = '';
|
||||
const msg = document.getElementById('pw-match-msg');
|
||||
if (msg) msg.remove();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user