05/24 Enhance functionalities
This commit is contained in:
@@ -52,6 +52,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span id="email-test-status" class="settings-test-status"></span>
|
||||
<button type="button" class="btn btn-ghost" onclick="testConnection('email')">📬 Test</button>
|
||||
<button class="btn btn-primary" type="submit">Save Email Settings</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -77,9 +79,52 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span id="groq-test-status" class="settings-test-status"></span>
|
||||
<button type="button" class="btn btn-ghost" onclick="testConnection('groq')">🔌 Test</button>
|
||||
<button class="btn btn-primary" type="submit">Save AI Settings</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function testConnection(type) {
|
||||
var url = type === 'email' ? '/admin/settings/test-email' : '/admin/settings/test-groq';
|
||||
var status = document.getElementById(type + '-test-status');
|
||||
var btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
status.textContent = '';
|
||||
status.className = 'settings-test-status';
|
||||
fetch(url, { method: 'POST', headers: { 'X-CSRFToken': getCsrfToken() } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) {
|
||||
status.classList.add('ok');
|
||||
status.textContent = type === 'email'
|
||||
? '✓ Test email sent to ' + d.to
|
||||
: '✓ Connected — model: ' + d.model;
|
||||
} else {
|
||||
status.classList.add('err');
|
||||
status.textContent = '✕ ' + d.error;
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
status.classList.add('err');
|
||||
status.textContent = '✕ Request failed';
|
||||
})
|
||||
.finally(function() {
|
||||
btn.disabled = false;
|
||||
btn.textContent = type === 'email' ? '📬 Test' : '🔌 Test';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; align-items: start; }
|
||||
.settings-test-status { font-size: .78rem; margin-right: auto; max-width: 260px; }
|
||||
.settings-test-status.ok { color: var(--success, #16a34a); }
|
||||
.settings-test-status.err { color: var(--danger, #dc2626); }
|
||||
@media(max-width:760px) { .settings-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
<td class="actions">
|
||||
<button class="btn btn-ghost btn-sm"
|
||||
onclick='openEditUser({{ u|tojson }})'>✎ Edit</button>
|
||||
{% if u.email %}
|
||||
<button class="btn btn-ghost btn-sm js-send-reset"
|
||||
data-id="{{ u.id }}" data-username="{{ u.username }}" data-email="{{ u.email }}"
|
||||
title="Send password reset link to {{ u.email }}">✉ Reset</button>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url_for('admin_users.delete', user_id=u.id) }}"
|
||||
style="display:inline"
|
||||
onsubmit="return confirm('Delete user {{ u.username }}?')">
|
||||
@@ -141,5 +146,34 @@ function openEditUser(u) {
|
||||
'/admin/users/' + u.id + '/edit';
|
||||
openModal('modal-edit-user');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.js-send-reset').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var username = btn.dataset.username;
|
||||
var email = btn.dataset.email;
|
||||
if (!confirm('Send a password reset link to ' + username + ' (' + email + ')?')) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
fetch('/admin/users/' + btn.dataset.id + '/send-reset', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': getCsrfToken() },
|
||||
}).then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) {
|
||||
btn.textContent = '✓ Sent';
|
||||
btn.style.color = 'var(--success, #16a34a)';
|
||||
} else {
|
||||
alert('Error: ' + d.error);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✉ Reset';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
alert('Request failed. Please try again.');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✉ Reset';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -113,7 +113,16 @@
|
||||
|
||||
<!-- History card -->
|
||||
<div class="card mt-2">
|
||||
<div class="card-header"><span class="card-title">Analysis History</span></div>
|
||||
<div class="card-header">
|
||||
<span class="card-title">Analysis History</span>
|
||||
<div class="flex gap-1" style="flex-wrap:wrap">
|
||||
<button class="ai-hist-filter active" data-verdict="">All</button>
|
||||
<button class="ai-hist-filter" data-verdict="PURSUE">PURSUE</button>
|
||||
<button class="ai-hist-filter" data-verdict="PASS">PASS</button>
|
||||
<button class="ai-hist-filter" data-verdict="UNCLEAR">UNCLEAR</button>
|
||||
<button class="ai-hist-filter" data-verdict="none">No verdict</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -484,6 +493,22 @@ function deleteAnalysis() {
|
||||
.catch(function() { alert('Delete failed. Please try again.'); });
|
||||
}
|
||||
|
||||
/* ─── History verdict filter ────────────────────────────────── */
|
||||
document.querySelectorAll('.ai-hist-filter').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.ai-hist-filter').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
var verdict = btn.dataset.verdict;
|
||||
document.querySelectorAll('.history-row').forEach(function(row) {
|
||||
if (!verdict) { row.style.display = ''; return; }
|
||||
var badge = row.querySelector('.badge');
|
||||
var rowVerdict = badge ? badge.textContent.trim() : '';
|
||||
var show = verdict === 'none' ? !badge : rowVerdict === verdict;
|
||||
row.style.display = show ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* ─── Utility ───────────────────────────────────────────────── */
|
||||
function escHtml(str) {
|
||||
return String(str || '').replace(/[&<>"']/g, function(c) {
|
||||
@@ -577,6 +602,13 @@ function escHtml(str) {
|
||||
|
||||
/* ── History ───────────────────────────────────────────────── */
|
||||
.history-row:hover td{background:var(--bg-subtle)}
|
||||
.ai-hist-filter{
|
||||
background:none;border:1px solid var(--border);
|
||||
padding:.2rem .55rem;font-size:.72rem;border-radius:var(--r-sm);
|
||||
cursor:pointer;color:var(--text-secondary);font-family:inherit;
|
||||
transition:background var(--t),color var(--t);
|
||||
}
|
||||
.ai-hist-filter.active{background:var(--accent);border-color:var(--accent);color:#fff}
|
||||
|
||||
@media(max-width:960px){
|
||||
.ai-layout{grid-template-columns:1fr}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
</div>
|
||||
|
||||
<button class="btn btn-secondary btn-sm" onclick="loadBids()">↻ Refresh</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="exportCsv()" title="Download current view as CSV">⬇ CSV</button>
|
||||
{% if session['user']['role'] == 'admin' %}
|
||||
<form method="post" action="{{ url_for('bid_tracker.send_reminders') }}" style="display:inline">
|
||||
<button class="btn btn-secondary btn-sm" type="submit" title="Email a digest of bids due within 7 days to admins">📧 Remind</button>
|
||||
@@ -172,6 +173,12 @@ var _allBids = []; // cached from last load
|
||||
var _bidOffset = 0;
|
||||
var _hasMore = false;
|
||||
var _PAGE_SIZE = 50;
|
||||
|
||||
function exportCsv() {
|
||||
var url = '/bids/export.csv';
|
||||
if (_activeStatus) url += '?status=' + encodeURIComponent(_activeStatus);
|
||||
window.location.href = url;
|
||||
}
|
||||
var _currentUserId = {{ session['user']['id'] }};
|
||||
var _isAdmin = {{ 'true' if session['user']['role'] == 'admin' else 'false' }};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user