06/04 Optimize app
This commit is contained in:
@@ -1073,7 +1073,24 @@ def get_user_active_shifts(user_id: int):
|
|||||||
|
|
||||||
# ─── Admin Dashboard ──────────────────────────────────────────────────────────
|
# ─── Admin Dashboard ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_admin_stats_cache: dict | None = None
|
||||||
|
_admin_stats_cached_at: datetime.datetime | None = None
|
||||||
|
_ADMIN_STATS_TTL_S = 60 # seconds
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_admin_stats_cache():
|
||||||
|
global _admin_stats_cache, _admin_stats_cached_at
|
||||||
|
_admin_stats_cache = None
|
||||||
|
_admin_stats_cached_at = None
|
||||||
|
|
||||||
|
|
||||||
def get_admin_dashboard_stats():
|
def get_admin_dashboard_stats():
|
||||||
|
global _admin_stats_cache, _admin_stats_cached_at
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if (_admin_stats_cache is not None and _admin_stats_cached_at is not None
|
||||||
|
and (now - _admin_stats_cached_at).total_seconds() < _ADMIN_STATS_TTL_S):
|
||||||
|
return _admin_stats_cache
|
||||||
|
|
||||||
conn = None
|
conn = None
|
||||||
try:
|
try:
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
@@ -1123,7 +1140,7 @@ def get_admin_dashboard_stats():
|
|||||||
)
|
)
|
||||||
ai_analyses_30d = cur.fetchone()["n"]
|
ai_analyses_30d = cur.fetchone()["n"]
|
||||||
cur.close()
|
cur.close()
|
||||||
return {
|
result = {
|
||||||
"user_stats": user_stats,
|
"user_stats": user_stats,
|
||||||
"total_sites": total_sites,
|
"total_sites": total_sites,
|
||||||
"total_users": total_users,
|
"total_users": total_users,
|
||||||
@@ -1132,6 +1149,9 @@ def get_admin_dashboard_stats():
|
|||||||
"bids_due_soon": bids_due_soon,
|
"bids_due_soon": bids_due_soon,
|
||||||
"ai_analyses_30d": ai_analyses_30d,
|
"ai_analyses_30d": ai_analyses_30d,
|
||||||
}
|
}
|
||||||
|
_admin_stats_cache = result
|
||||||
|
_admin_stats_cached_at = now
|
||||||
|
return result
|
||||||
finally:
|
finally:
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ def analyze():
|
|||||||
f"AI analysis on {len(file_names)} file(s). Verdict: {result.get('verdict')}.")
|
f"AI analysis on {len(file_names)} file(s). Verdict: {result.get('verdict')}.")
|
||||||
|
|
||||||
return jsonify({"analysis_id": analysis_id, "model": model,
|
return jsonify({"analysis_id": analysis_id, "model": model,
|
||||||
"file_count": len(file_names), **result})
|
"file_count": len(file_names), "file_errors": errors, **result})
|
||||||
|
|
||||||
|
|
||||||
# Magic-byte signatures for each supported binary format.
|
# Magic-byte signatures for each supported binary format.
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
alert.style.transition = 'opacity 0.5s';
|
alert.style.transition = 'opacity 0.5s';
|
||||||
alert.style.opacity = '0';
|
alert.style.opacity = '0';
|
||||||
setTimeout(() => alert.remove(), 500);
|
setTimeout(() => alert.remove(), 500);
|
||||||
}, 5000);
|
}, 7000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -358,7 +358,13 @@ async function runAnalysis() {
|
|||||||
|
|
||||||
var btn = document.getElementById('btn-analyze');
|
var btn = document.getElementById('btn-analyze');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = '⏳ Analysing…';
|
|
||||||
|
var _elapsed = 0;
|
||||||
|
var _elapsedTimer = setInterval(function() {
|
||||||
|
_elapsed++;
|
||||||
|
btn.textContent = '⏳ Analysing… ' + _elapsed + 's';
|
||||||
|
}, 1000);
|
||||||
|
btn.textContent = '⏳ Analysing… 0s';
|
||||||
|
|
||||||
document.getElementById('ai-output').innerHTML =
|
document.getElementById('ai-output').innerHTML =
|
||||||
'<div class="ai-placeholder"><p class="text-muted">Running AI analysis… this may take a moment.</p></div>';
|
'<div class="ai-placeholder"><p class="text-muted">Running AI analysis… this may take a moment.</p></div>';
|
||||||
@@ -384,11 +390,21 @@ async function runAnalysis() {
|
|||||||
data.model = model || '{{ groq_model }}';
|
data.model = model || '{{ groq_model }}';
|
||||||
data.criteria_count = {{ criteria | selectattr('is_active') | list | length }};
|
data.criteria_count = {{ criteria | selectattr('is_active') | list | length }};
|
||||||
showResult(data);
|
showResult(data);
|
||||||
|
if (data.file_errors && data.file_errors.length) {
|
||||||
|
var warn = document.createElement('div');
|
||||||
|
warn.className = 'alert alert-warning';
|
||||||
|
warn.style.cssText = 'margin:.75rem 0 0';
|
||||||
|
warn.innerHTML = '⚠ ' + data.file_errors.length + ' file(s) skipped:<ul style="margin:.25rem 0 0 1.25rem;padding:0">'
|
||||||
|
+ data.file_errors.map(function(e){ return '<li>' + escHtml(e) + '</li>'; }).join('')
|
||||||
|
+ '</ul>';
|
||||||
|
document.getElementById('ai-output').prepend(warn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
document.getElementById('ai-output').innerHTML =
|
document.getElementById('ai-output').innerHTML =
|
||||||
'<div class="alert alert-danger" style="margin:1rem">Request failed: ' + escHtml(String(e)) + '</div>';
|
'<div class="alert alert-danger" style="margin:1rem">Request failed: ' + escHtml(String(e)) + '</div>';
|
||||||
} finally {
|
} finally {
|
||||||
|
clearInterval(_elapsedTimer);
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = '🔍 Analyze with AI';
|
btn.textContent = '🔍 Analyze with AI';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Due Date</label>
|
<label>Due Date</label>
|
||||||
<input type="date" name="due_date">
|
<input type="date" name="due_date" id="add-bid-due-date">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -481,6 +481,9 @@ function esc(str) {
|
|||||||
|
|
||||||
/* ── Init ───────────────────────────────────────────────── */
|
/* ── Init ───────────────────────────────────────────────── */
|
||||||
loadBids();
|
loadBids();
|
||||||
|
|
||||||
|
/* Set today as the minimum selectable due date for new bids */
|
||||||
|
document.getElementById('add-bid-due-date').min = new Date().toISOString().slice(0, 10);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user