June 25 - Optimize codes

This commit is contained in:
2026-06-25 16:40:10 -04:00
parent b4cb292afa
commit ea3648ad90
10 changed files with 85 additions and 56 deletions
+2 -1
View File
@@ -18,7 +18,8 @@ def dashboard():
stats = get_admin_dashboard_stats() stats = get_admin_dashboard_stats()
except Exception as e: except Exception as e:
logger.error(f"Dashboard stats error: {e}") logger.error(f"Dashboard stats error: {e}")
stats = {"user_stats": [], "total_sites": 0, "total_users": 0, "active_today": 0} stats = {"user_stats": [], "total_sites": 0, "total_users": 0, "active_today": 0,
"open_bids": 0, "bids_due_soon": 0, "ai_analyses_30d": 0}
try: try:
missed = get_missed_shifts_today() missed = get_missed_shifts_today()
except Exception as e: except Exception as e:
+4
View File
@@ -7,6 +7,7 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
from models import ( from models import (
get_all_users, create_user, update_user, delete_user, get_all_users, create_user, update_user, delete_user,
get_user_by_id, create_password_reset_token, log_action, get_user_by_id, create_password_reset_token, log_action,
invalidate_admin_stats_cache,
) )
from utils.decorators import admin_required from utils.decorators import admin_required
from utils.email import send_email from utils.email import send_email
@@ -38,6 +39,7 @@ def create():
try: try:
create_user(admin_id, username, password, role, full_name, email) create_user(admin_id, username, password, role, full_name, email)
invalidate_admin_stats_cache()
flash(f"User '{username}' created successfully.", "success") flash(f"User '{username}' created successfully.", "success")
logger.info(f"User '{username}' created by admin_id={admin_id}.") logger.info(f"User '{username}' created by admin_id={admin_id}.")
except Exception as e: except Exception as e:
@@ -60,6 +62,7 @@ def edit(user_id):
try: try:
update_user(admin_id, user_id, username, role, full_name, is_active, password, email) update_user(admin_id, user_id, username, role, full_name, is_active, password, email)
invalidate_admin_stats_cache()
flash(f"User '{username}' updated successfully.", "success") flash(f"User '{username}' updated successfully.", "success")
logger.info(f"User id={user_id} updated by admin_id={admin_id}.") logger.info(f"User id={user_id} updated by admin_id={admin_id}.")
except ValueError as e: except ValueError as e:
@@ -77,6 +80,7 @@ def delete(user_id):
admin_id = session["user"]["id"] admin_id = session["user"]["id"]
try: try:
delete_user(admin_id, user_id) delete_user(admin_id, user_id)
invalidate_admin_stats_cache()
flash("User deleted successfully.", "success") flash("User deleted successfully.", "success")
logger.info(f"User id={user_id} deleted by admin_id={admin_id}.") logger.info(f"User id={user_id} deleted by admin_id={admin_id}.")
except ValueError as e: except ValueError as e:
+4 -1
View File
@@ -7,7 +7,7 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
from models import ( from models import (
get_all_websites, get_website_by_id, get_website_credentials, get_all_websites, get_website_by_id, get_website_credentials,
get_website_assigned_users, create_website, update_website, delete_website, get_website_assigned_users, create_website, update_website, delete_website,
get_all_users, get_all_users, invalidate_admin_stats_cache,
) )
from utils.decorators import admin_required from utils.decorators import admin_required
@@ -78,6 +78,7 @@ def create():
try: try:
create_website(admin_id, name, url, check_type, note, creds, visibility, assigned) create_website(admin_id, name, url, check_type, note, creds, visibility, assigned)
invalidate_admin_stats_cache()
flash(f"Website '{name}' created successfully.", "success") flash(f"Website '{name}' created successfully.", "success")
logger.info(f"Website '{name}' created by admin_id={admin_id}.") logger.info(f"Website '{name}' created by admin_id={admin_id}.")
except Exception as e: except Exception as e:
@@ -101,6 +102,7 @@ def edit(website_id):
try: try:
update_website(admin_id, website_id, name, url, check_type, note, creds, visibility, assigned) update_website(admin_id, website_id, name, url, check_type, note, creds, visibility, assigned)
invalidate_admin_stats_cache()
flash(f"Website '{name}' updated successfully.", "success") flash(f"Website '{name}' updated successfully.", "success")
logger.info(f"Website id={website_id} updated by admin_id={admin_id}.") logger.info(f"Website id={website_id} updated by admin_id={admin_id}.")
except Exception as e: except Exception as e:
@@ -116,6 +118,7 @@ def delete(website_id):
admin_id = session["user"]["id"] admin_id = session["user"]["id"]
try: try:
delete_website(admin_id, website_id) delete_website(admin_id, website_id)
invalidate_admin_stats_cache()
flash("Website deleted (soft) successfully.", "success") flash("Website deleted (soft) successfully.", "success")
logger.info(f"Website id={website_id} soft-deleted by admin_id={admin_id}.") logger.info(f"Website id={website_id} soft-deleted by admin_id={admin_id}.")
except Exception as e: except Exception as e:
+4
View File
@@ -11,6 +11,7 @@ from models import (
get_all_bids, get_bid, create_bid, update_bid, delete_bid, get_all_bids, get_bid, create_bid, update_bid, delete_bid,
get_bid_updates, add_bid_update, delete_bid_update, BID_STATUSES, get_bid_updates, add_bid_update, delete_bid_update, BID_STATUSES,
log_action, get_bids_due_soon, get_admin_emails, log_action, get_bids_due_soon, get_admin_emails,
invalidate_admin_stats_cache,
) )
from utils.decorators import login_required, admin_required from utils.decorators import login_required, admin_required
from utils.email import send_email from utils.email import send_email
@@ -78,6 +79,7 @@ def create():
bid_id = create_bid(user["id"], title, url, source, sol_no, status, due_date, notes) bid_id = create_bid(user["id"], title, url, source, sol_no, status, due_date, notes)
log_action(user["id"], "CREATE_BID", "bid_tracker", bid_id, log_action(user["id"], "CREATE_BID", "bid_tracker", bid_id,
f"Created bid '{title}' status='{status}'.") f"Created bid '{title}' status='{status}'.")
invalidate_admin_stats_cache()
flash(f"Bid '{title}' added.", "success") flash(f"Bid '{title}' added.", "success")
logger.info(f"Bid '{title}' created by user_id={user['id']}.") logger.info(f"Bid '{title}' created by user_id={user['id']}.")
except Exception as e: except Exception as e:
@@ -103,6 +105,7 @@ def edit(bid_id):
update_bid(user["id"], bid_id, title, url, source, sol_no, status, due_date, notes) update_bid(user["id"], bid_id, title, url, source, sol_no, status, due_date, notes)
log_action(user["id"], "UPDATE_BID", "bid_tracker", bid_id, log_action(user["id"], "UPDATE_BID", "bid_tracker", bid_id,
f"Updated bid id={bid_id} status='{status}'.") f"Updated bid id={bid_id} status='{status}'.")
invalidate_admin_stats_cache()
flash(f"Bid '{title}' updated.", "success") flash(f"Bid '{title}' updated.", "success")
logger.info(f"Bid id={bid_id} updated by user_id={user['id']}.") logger.info(f"Bid id={bid_id} updated by user_id={user['id']}.")
except Exception as e: except Exception as e:
@@ -120,6 +123,7 @@ def delete(bid_id):
delete_bid(user["id"], bid_id) delete_bid(user["id"], bid_id)
log_action(user["id"], "DELETE_BID", "bid_tracker", bid_id, log_action(user["id"], "DELETE_BID", "bid_tracker", bid_id,
f"Deleted bid id={bid_id}.") f"Deleted bid id={bid_id}.")
invalidate_admin_stats_cache()
flash("Bid deleted.", "success") flash("Bid deleted.", "success")
logger.info(f"Bid id={bid_id} deleted by user_id={user['id']}.") logger.info(f"Bid id={bid_id} deleted by user_id={user['id']}.")
except Exception as e: except Exception as e:
+8 -32
View File
@@ -183,6 +183,7 @@ document.addEventListener('DOMContentLoaded', () => {
const SESSION_MS = 30 * 60 * 1000; const SESSION_MS = 30 * 60 * 1000;
var warningTimer = null, expireTimer = null, countdownInterval = null; var warningTimer = null, expireTimer = null, countdownInterval = null;
var _lastPing = 0;
function isWarningShowing() { function isWarningShowing() {
var m = document.getElementById('modal-session-warning'); var m = document.getElementById('modal-session-warning');
@@ -208,8 +209,15 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function resetTimers() { function resetTimers() {
var now = Date.now();
clearTimeout(warningTimer); clearTimeout(expireTimer); clearCountdown(); clearTimeout(warningTimer); clearTimeout(expireTimer); clearCountdown();
if (isWarningShowing()) closeModal('modal-session-warning'); if (isWarningShowing()) closeModal('modal-session-warning');
// Ping the server at most once per minute so the server-side session
// stays alive while the user is active on the page.
if (now - _lastPing > 60000) {
_lastPing = now;
fetch('/ping', { credentials: 'same-origin' }).catch(function(){});
}
warningTimer = setTimeout(function() { warningTimer = setTimeout(function() {
openModal('modal-session-warning'); openModal('modal-session-warning');
@@ -311,38 +319,6 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
}); });
/* ── Password strength meter ───────────────────────────────── */
function _pwStrengthLevel(pw) {
var score = 0;
if (pw.length >= 8) score++;
if (pw.length >= 12) score++;
if (/[A-Z]/.test(pw)) score++;
if (/[0-9]/.test(pw)) score++;
if (/[^A-Za-z0-9]/.test(pw)) score++;
var map = ['pw-weak','pw-weak','pw-fair','pw-strong','pw-great','pw-great'];
var lbl = ['Weak','Weak','Fair','Strong','Very strong','Very strong'];
return { cls: map[score], label: lbl[score] };
}
function attachPasswordStrength(inputId, fillId, labelId) {
var input = document.getElementById(inputId);
var fill = document.getElementById(fillId);
var lbl = document.getElementById(labelId);
if (!input || !fill || !lbl) return;
input.addEventListener('input', function() {
if (!this.value) {
fill.className = 'pw-strength-fill';
lbl.className = 'pw-strength-label';
lbl.textContent = '';
return;
}
var r = _pwStrengthLevel(this.value);
fill.className = 'pw-strength-fill ' + r.cls;
lbl.className = 'pw-strength-label ' + r.cls;
lbl.textContent = r.label;
});
}
/* ── HTML escape (safe for attributes and text nodes) ──────── */ /* ── HTML escape (safe for attributes and text nodes) ──────── */
function esc(str) { function esc(str) {
return String(str || '').replace(/[&<>"']/g, function(c) { return String(str || '').replace(/[&<>"']/g, function(c) {
+31
View File
@@ -0,0 +1,31 @@
/* ── Password strength meter (shared by base.html and standalone pages) ── */
function _pwStrengthLevel(pw) {
var score = 0;
if (pw.length >= 8) score++;
if (pw.length >= 12) score++;
if (/[A-Z]/.test(pw)) score++;
if (/[0-9]/.test(pw)) score++;
if (/[^A-Za-z0-9]/.test(pw)) score++;
var map = ['pw-weak','pw-weak','pw-fair','pw-strong','pw-great','pw-great'];
var lbl = ['Weak','Weak','Fair','Strong','Very strong','Very strong'];
return { cls: map[score], label: lbl[score] };
}
function attachPasswordStrength(inputId, fillId, labelId) {
var input = document.getElementById(inputId);
var fill = document.getElementById(fillId);
var lbl = document.getElementById(labelId);
if (!input || !fill || !lbl) return;
input.addEventListener('input', function() {
if (!this.value) {
fill.className = 'pw-strength-fill';
lbl.className = 'pw-strength-label';
lbl.textContent = '';
return;
}
var r = _pwStrengthLevel(this.value);
fill.className = 'pw-strength-fill ' + r.cls;
lbl.className = 'pw-strength-label ' + r.cls;
lbl.textContent = r.label;
});
}
+9 -2
View File
@@ -31,8 +31,8 @@
<td><span class="badge badge-{{ 'success' if u.is_active else 'danger' }}">{{ 'Yes' if u.is_active else 'No' }}</span></td> <td><span class="badge badge-{{ 'success' if u.is_active else 'danger' }}">{{ 'Yes' if u.is_active else 'No' }}</span></td>
<td class="text-muted">{{ u.created_at.strftime('%Y-%m-%d') if u.created_at else '—' }}</td> <td class="text-muted">{{ u.created_at.strftime('%Y-%m-%d') if u.created_at else '—' }}</td>
<td class="actions"> <td class="actions">
<button class="btn btn-ghost btn-sm" <button class="btn btn-ghost btn-sm js-edit-user"
onclick='openEditUser({{ u|tojson }})'>✎ Edit</button> data-user="{{ u|tojson|e }}">✎ Edit</button>
{% if u.email %} {% if u.email %}
<button class="btn btn-ghost btn-sm js-send-reset" <button class="btn btn-ghost btn-sm js-send-reset"
data-id="{{ u.id }}" data-username="{{ u.username }}" data-email="{{ u.email }}" data-id="{{ u.id }}" data-username="{{ u.username }}" data-email="{{ u.email }}"
@@ -170,6 +170,13 @@ document.querySelectorAll('.js-delete-user').forEach(function(btn) {
}); });
}); });
/* Edit user — data-user attribute avoids single-quote apostrophe breakage */
document.querySelectorAll('.js-edit-user').forEach(function(btn) {
btn.addEventListener('click', function() {
openEditUser(JSON.parse(btn.dataset.user));
});
});
function openEditUser(u) { function openEditUser(u) {
document.getElementById('edit-username').value = u.username; document.getElementById('edit-username').value = u.username;
document.getElementById('edit-full-name').value = u.full_name || ''; document.getElementById('edit-full-name').value = u.full_name || '';
+1
View File
@@ -86,6 +86,7 @@
</div> </div>
</div> </div>
<script src="{{ url_for('static', filename='js/pw-strength.js') }}"></script>
<script src="{{ url_for('static', filename='js/app.js') }}"></script> <script src="{{ url_for('static', filename='js/app.js') }}"></script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>
+2 -18
View File
@@ -46,23 +46,7 @@
</p> </p>
</div> </div>
<script> <script src="{{ url_for('static', filename='js/pw-strength.js') }}"></script>
(function() { <script>attachPasswordStrength('password', 'pw-fill', 'pw-label');</script>
var input = document.getElementById('password');
var fill = document.getElementById('pw-fill');
var lbl = document.getElementById('pw-label');
var map = ['pw-weak','pw-weak','pw-fair','pw-strong','pw-great','pw-great'];
var lbls = ['Weak','Weak','Fair','Strong','Very strong','Very strong'];
input.addEventListener('input', function() {
if (!this.value) { fill.className='pw-strength-fill'; lbl.className='pw-strength-label'; lbl.textContent=''; return; }
var pw=this.value, s=0;
if (pw.length>=8) s++; if (pw.length>=12) s++;
if (/[A-Z]/.test(pw)) s++; if (/[0-9]/.test(pw)) s++; if (/[^A-Za-z0-9]/.test(pw)) s++;
fill.className='pw-strength-fill '+map[s];
lbl.className='pw-strength-label '+map[s];
lbl.textContent=lbls[s];
});
}());
</script>
</body> </body>
</html> </html>
+20 -2
View File
@@ -293,13 +293,22 @@ document.getElementById('site-list').addEventListener('click', function(e) {
method: 'POST', credentials: 'same-origin', method: 'POST', credentials: 'same-origin',
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': getCsrfToken()}, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': getCsrfToken()},
body: 'user_note=' body: 'user_note='
}).then(function() { }).then(function(r) {
if (!r.ok) throw new Error('Server error ' + r.status);
var toast = document.createElement('div'); var toast = document.createElement('div');
toast.className = 'alert alert-success'; toast.className = 'alert alert-success';
toast.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:9999;min-width:220px;box-shadow:var(--shadow)'; toast.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:9999;min-width:220px;box-shadow:var(--shadow)';
toast.textContent = '✔ ' + siteName + ' marked checked'; toast.textContent = '✔ ' + siteName + ' marked checked';
document.body.appendChild(toast); document.body.appendChild(toast);
setTimeout(function() { location.reload(); }, 700); setTimeout(function() { location.reload(); }, 700);
}).catch(function(err) {
btn.disabled = false;
var toast = document.createElement('div');
toast.className = 'alert alert-danger';
toast.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:9999;min-width:220px;box-shadow:var(--shadow)';
toast.textContent = '✖ Failed to mark checked. Please try again.';
document.body.appendChild(toast);
setTimeout(function() { toast.remove(); }, 4000);
}); });
return; return;
} }
@@ -353,13 +362,22 @@ document.getElementById('site-list').addEventListener('click', function(e) {
method: 'POST', credentials: 'same-origin', method: 'POST', credentials: 'same-origin',
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': getCsrfToken()}, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': getCsrfToken()},
body: '' body: ''
}).then(function() { }).then(function(r) {
if (!r.ok) throw new Error('Server error ' + r.status);
var toast = document.createElement('div'); var toast = document.createElement('div');
toast.className = 'alert alert-info'; toast.className = 'alert alert-info';
toast.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:9999;min-width:220px;box-shadow:var(--shadow)'; toast.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:9999;min-width:220px;box-shadow:var(--shadow)';
toast.textContent = '↩ Check removed for ' + siteName; toast.textContent = '↩ Check removed for ' + siteName;
document.body.appendChild(toast); document.body.appendChild(toast);
setTimeout(function() { location.reload(); }, 700); setTimeout(function() { location.reload(); }, 700);
}).catch(function(err) {
btn.disabled = false;
var toast = document.createElement('div');
toast.className = 'alert alert-danger';
toast.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:9999;min-width:220px;box-shadow:var(--shadow)';
toast.textContent = '✖ Failed to remove check. Please try again.';
document.body.appendChild(toast);
setTimeout(function() { toast.remove(); }, 4000);
}); });
return; return;
} }