05/24 Fix bugs
This commit is contained in:
@@ -1668,3 +1668,31 @@ def consume_password_reset_token(token: str, new_password: str) -> bool:
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
|
||||
def purge_expired_reset_tokens():
|
||||
"""Delete all expired password reset tokens."""
|
||||
conn = None
|
||||
try:
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
cur.execute("DELETE FROM password_reset_tokens WHERE expires_at < NOW()")
|
||||
conn.commit()
|
||||
cur.close()
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
|
||||
def delete_ai_analysis(analysis_id: int):
|
||||
"""Permanently delete an AI analysis record."""
|
||||
conn = None
|
||||
try:
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
cur.execute("DELETE FROM ai_analysis_log WHERE id=%s", (analysis_id,))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
+24
-8
@@ -13,7 +13,7 @@ from flask import (Blueprint, render_template, request, redirect, url_for,
|
||||
from models import (
|
||||
get_all_criteria, get_active_criteria, create_criterion, update_criterion,
|
||||
delete_criterion, save_ai_analysis, get_ai_analysis_history,
|
||||
get_ai_analysis_detail, log_action,
|
||||
get_ai_analysis_detail, delete_ai_analysis, log_action,
|
||||
)
|
||||
from config import get_setting
|
||||
from utils.decorators import login_required, admin_required
|
||||
@@ -370,11 +370,27 @@ def analysis_detail(analysis_id):
|
||||
if user["role"] != "admin" and record["user_id"] != user["id"]:
|
||||
return jsonify({"error": "Access denied"}), 403
|
||||
return jsonify({
|
||||
"id": record["id"],
|
||||
"username": record.get("username"),
|
||||
"file_names": record["file_names"],
|
||||
"model": record["model"],
|
||||
"verdict": record["verdict"],
|
||||
"analyzed_at": str(record["analyzed_at"]),
|
||||
"summary_text": record["summary_text"],
|
||||
"id": record["id"],
|
||||
"username": record.get("username"),
|
||||
"file_names": record["file_names"],
|
||||
"model": record["model"],
|
||||
"verdict": record["verdict"],
|
||||
"analyzed_at": str(record["analyzed_at"]),
|
||||
"summary_text": record["summary_text"],
|
||||
"criteria_snapshot": record.get("criteria_snapshot") or "",
|
||||
})
|
||||
|
||||
|
||||
@ai_summary_bp.route("/history/<int:analysis_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def delete_analysis(analysis_id):
|
||||
record = get_ai_analysis_detail(analysis_id)
|
||||
if not record:
|
||||
return jsonify({"error": "Not found"}), 404
|
||||
user = session["user"]
|
||||
if user["role"] != "admin" and record["user_id"] != user["id"]:
|
||||
return jsonify({"error": "Access denied"}), 403
|
||||
delete_ai_analysis(analysis_id)
|
||||
log_action(user["id"], "DELETE_AI_ANALYSIS", "ai_analysis_log", analysis_id,
|
||||
f"Deleted AI analysis id={analysis_id}.")
|
||||
return jsonify({"ok": True})
|
||||
|
||||
@@ -8,6 +8,7 @@ from models import (
|
||||
authenticate, check_login_allowed, change_password, log_action,
|
||||
get_user_by_email, create_password_reset_token,
|
||||
get_password_reset_user, consume_password_reset_token,
|
||||
purge_expired_reset_tokens,
|
||||
)
|
||||
from utils.decorators import login_required
|
||||
from utils.email import send_email
|
||||
@@ -46,6 +47,10 @@ def login():
|
||||
"role": user["role"],
|
||||
"email": user.get("email", ""),
|
||||
}
|
||||
try:
|
||||
purge_expired_reset_tokens()
|
||||
except Exception:
|
||||
pass # non-critical cleanup; never fail a login because of it
|
||||
logger.info(f"User '{username}' logged in from {ip}.")
|
||||
flash(f"You have been signed in. Welcome, {session['user']['full_name']}!", "success")
|
||||
if user["role"] == "admin":
|
||||
|
||||
@@ -220,5 +220,29 @@ function buildShiftForm(s) {
|
||||
<textarea class="form-control" name="note" rows="2">${esc(s.note)}</textarea></div>`;
|
||||
}
|
||||
function esc(s) { return (s||'').replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<'); }
|
||||
|
||||
// Highlight today's column in the calendar
|
||||
(function() {
|
||||
var jsDay = new Date().getDay(); // 0=Sun … 6=Sat
|
||||
var mysqlDay = String(jsDay === 0 ? 1 : jsDay + 1); // MySQL DAYOFWEEK: 1=Sun, 2=Mon…
|
||||
var colIdx = DAY_MAP.findIndex(function(d) { return d[1] === mysqlDay; });
|
||||
if (colIdx === -1) return;
|
||||
var calTable = document.querySelector('#tab-calendar table');
|
||||
if (!calTable) return;
|
||||
calTable.querySelectorAll('tr').forEach(function(tr) {
|
||||
var cells = tr.querySelectorAll('th, td');
|
||||
var cell = cells[colIdx + 1]; // +1 for the leading Shift name column
|
||||
if (cell) cell.classList.add('cal-today');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cal-today { background: #eff6ff !important; }
|
||||
.cal-today th, thead .cal-today {
|
||||
background: #dbeafe !important;
|
||||
font-weight: 700;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -233,9 +233,11 @@
|
||||
<div id="hist-verdict-banner" style="display:none"></div>
|
||||
<div class="modal-body" style="padding:0">
|
||||
<div class="ai-meta-bar" id="hist-meta"></div>
|
||||
<div id="hist-body" class="ai-rendered-output" style="max-height:65vh;overflow-y:auto;padding:1.25rem 1.5rem"></div>
|
||||
<div id="hist-criteria" style="display:none;padding:.6rem 1.5rem;font-size:.78rem;background:var(--bg-subtle);border-bottom:1px solid var(--border)"></div>
|
||||
<div id="hist-body" class="ai-rendered-output" style="max-height:60vh;overflow-y:auto;padding:1.25rem 1.5rem"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-danger btn-sm" id="btn-delete-analysis" style="margin-right:auto;display:none" onclick="deleteAnalysis()">🗑 Delete</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal('modal-history')">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -407,13 +409,18 @@ document.querySelectorAll('.js-edit-criterion').forEach(function(btn) {
|
||||
});
|
||||
|
||||
/* ─── History rows ──────────────────────────────────────────── */
|
||||
var _currentAnalysisId = null;
|
||||
|
||||
document.querySelectorAll('.history-row').forEach(function(row) {
|
||||
row.addEventListener('click', function() {
|
||||
var id = row.dataset.id;
|
||||
_currentAnalysisId = id;
|
||||
document.getElementById('hist-title').textContent = 'Loading…';
|
||||
document.getElementById('hist-meta').textContent = '';
|
||||
document.getElementById('hist-body').innerHTML = '';
|
||||
document.getElementById('hist-criteria').style.display = 'none';
|
||||
document.getElementById('hist-verdict-banner').style.display = 'none';
|
||||
document.getElementById('btn-delete-analysis').style.display = 'none';
|
||||
openModal('modal-history');
|
||||
|
||||
fetch('/ai-summary/history/' + id)
|
||||
@@ -435,8 +442,23 @@ document.querySelectorAll('.history-row').forEach(function(row) {
|
||||
vb.innerHTML = verdictBannerHTML(d.verdict);
|
||||
vb.style.display = '';
|
||||
}
|
||||
|
||||
// Criteria snapshot
|
||||
var criteriaEl = document.getElementById('hist-criteria');
|
||||
if (d.criteria_snapshot) {
|
||||
try {
|
||||
var snap = JSON.parse(d.criteria_snapshot);
|
||||
if (snap && snap.length) {
|
||||
var labels = snap.map(function(c) { return c.title; }).join(' · ');
|
||||
criteriaEl.innerHTML = '<strong style="color:var(--text)">Criteria used:</strong> ' + escHtml(labels);
|
||||
criteriaEl.style.display = '';
|
||||
}
|
||||
} catch(e) { /* ignore malformed snapshot */ }
|
||||
}
|
||||
|
||||
document.getElementById('hist-body').innerHTML =
|
||||
renderAI(d.summary_text || '');
|
||||
document.getElementById('btn-delete-analysis').style.display = '';
|
||||
})
|
||||
.catch(function() {
|
||||
document.getElementById('hist-body').textContent = 'Failed to load analysis.';
|
||||
@@ -444,6 +466,24 @@ document.querySelectorAll('.history-row').forEach(function(row) {
|
||||
});
|
||||
});
|
||||
|
||||
function deleteAnalysis() {
|
||||
if (!_currentAnalysisId) return;
|
||||
if (!confirm('Delete this analysis? This cannot be undone.')) return;
|
||||
fetch('/ai-summary/history/' + _currentAnalysisId + '/delete', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': getCsrfToken() },
|
||||
}).then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.error) { alert(d.error); return; }
|
||||
closeModal('modal-history');
|
||||
// Remove the row from the table
|
||||
var row = document.querySelector('.history-row[data-id="' + _currentAnalysisId + '"]');
|
||||
if (row) row.remove();
|
||||
_currentAnalysisId = null;
|
||||
})
|
||||
.catch(function() { alert('Delete failed. Please try again.'); });
|
||||
}
|
||||
|
||||
/* ─── Utility ───────────────────────────────────────────────── */
|
||||
function escHtml(str) {
|
||||
return String(str || '').replace(/[&<>"']/g, function(c) {
|
||||
|
||||
Reference in New Issue
Block a user