06/04 Optimize app
This commit is contained in:
@@ -69,6 +69,22 @@
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- ── Session timeout warning modal ──────────────────────── -->
|
||||
<div class="modal-overlay" id="modal-session-warning" data-no-backdrop-close>
|
||||
<div class="modal" style="max-width:380px">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">⏱ Session Expiring Soon</span>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align:center">
|
||||
<p style="margin:0 0 .5rem">Your session expires in <strong id="session-countdown">5:00</strong>.</p>
|
||||
<p class="text-muted" style="font-size:.875rem;margin:0">Click below to stay logged in.</p>
|
||||
</div>
|
||||
<div class="modal-footer" style="justify-content:center">
|
||||
<button class="btn btn-primary" id="btn-stay-logged-in">Stay Logged In</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -258,9 +258,12 @@ function renderBidList(bids) {
|
||||
var dueD = new Date(b.due_date.slice(0, 10)); dueD.setHours(0, 0, 0, 0);
|
||||
var diff = Math.round((dueD - today) / 86400000);
|
||||
if (diff < 0) {
|
||||
urgencyBadge = '<span class="badge badge-danger badge-sm">Overdue</span>';
|
||||
var od = Math.abs(diff);
|
||||
urgencyBadge = '<span class="badge badge-danger badge-sm">Overdue by ' + od + (od === 1 ? ' day' : ' days') + '</span>';
|
||||
} else if (diff === 0) {
|
||||
urgencyBadge = '<span class="badge badge-danger badge-sm">Due today</span>';
|
||||
} else if (diff <= 7) {
|
||||
urgencyBadge = '<span class="badge badge-warning badge-sm">Due Soon</span>';
|
||||
urgencyBadge = '<span class="badge badge-warning badge-sm">Due in ' + diff + (diff === 1 ? ' day' : ' days') + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
|
||||
<div class="sc-select">
|
||||
<input type="checkbox" class="bulk-cb" data-id="{{ site.id }}"
|
||||
aria-label="Select {{ site.name }} for bulk check"
|
||||
{% if site.check_id %}disabled{% endif %}>
|
||||
</div>
|
||||
|
||||
@@ -209,7 +210,7 @@
|
||||
<div class="text-center text-muted">Loading…</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeModal('modal-creds')">Close</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal('modal-creds'); document.getElementById('creds-body').innerHTML=''">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,24 +233,28 @@ function toggleGroup(name) {
|
||||
}
|
||||
|
||||
/* ── Search — shows matching cards, auto-expands collapsed groups ── */
|
||||
var _searchTimer = null;
|
||||
document.getElementById('site-search').addEventListener('input', function() {
|
||||
var q = this.value.trim().toLowerCase();
|
||||
document.querySelectorAll('.site-card').forEach(function(card) {
|
||||
var match = !q || card.dataset.name.includes(q) || card.dataset.url.includes(q);
|
||||
card.style.display = match ? '' : 'none';
|
||||
var val = this.value;
|
||||
clearTimeout(_searchTimer);
|
||||
_searchTimer = setTimeout(function() {
|
||||
var q = val.trim().toLowerCase();
|
||||
document.querySelectorAll('.site-card').forEach(function(card) {
|
||||
var match = !q || card.dataset.name.includes(q) || card.dataset.url.includes(q);
|
||||
card.style.display = match ? '' : 'none';
|
||||
|
||||
// Auto-expand the group containing a matched card
|
||||
if (match && q) {
|
||||
var group = card.closest('.site-group');
|
||||
if (group) {
|
||||
group.style.display = '';
|
||||
var name = group.id.replace('group-', '');
|
||||
var toggle = document.getElementById('toggle-' + name);
|
||||
if (toggle) toggle.textContent = '▾';
|
||||
_collapsed[name] = false;
|
||||
if (match && q) {
|
||||
var group = card.closest('.site-group');
|
||||
if (group) {
|
||||
group.style.display = '';
|
||||
var name = group.id.replace('group-', '');
|
||||
var toggle = document.getElementById('toggle-' + name);
|
||||
if (toggle) toggle.textContent = '▾';
|
||||
_collapsed[name] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
|
||||
/* ── Bulk select ───────────────────────────────────────────── */
|
||||
@@ -282,11 +287,20 @@ document.getElementById('site-list').addEventListener('click', function(e) {
|
||||
|
||||
/* Mark Checked */
|
||||
if (btn.classList.contains('js-check')) {
|
||||
var siteName = btn.dataset.name;
|
||||
btn.disabled = true;
|
||||
fetch('/dashboard/check/' + btn.dataset.id, {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': getCsrfToken()},
|
||||
body: 'user_note='
|
||||
}).then(function() { location.reload(); });
|
||||
}).then(function() {
|
||||
var toast = document.createElement('div');
|
||||
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.textContent = '✔ ' + siteName + ' marked checked';
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(function() { location.reload(); }, 700);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,6 +354,11 @@ document.getElementById('site-list').addEventListener('click', function(e) {
|
||||
|
||||
/* ── Credential modal actions ──────────────────────────────── */
|
||||
document.getElementById('modal-creds').addEventListener('click', function(e) {
|
||||
/* Clear body when clicking the backdrop */
|
||||
if (e.target === this) {
|
||||
document.getElementById('creds-body').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
var copyBtn = e.target.closest('[data-copy]');
|
||||
if (copyBtn) { copyToClipboard(copyBtn.dataset.copy, copyBtn); return; }
|
||||
var toggleBtn = e.target.closest('[data-toggle-pw]');
|
||||
@@ -359,30 +378,40 @@ function esc(str) {
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Health dots (server-side probe) ───────────────────────── */
|
||||
document.querySelectorAll('.site-card').forEach(function(card) {
|
||||
var id = card.dataset.id;
|
||||
var dot = document.getElementById('health-' + id);
|
||||
if (!dot) return;
|
||||
fetch('/dashboard/health/' + id, {credentials: 'same-origin'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.status === 'ok') {
|
||||
dot.style.color = d.ms > 3000 ? '#d97706' : '#16a34a';
|
||||
dot.title = 'Reachable (' + d.ms + 'ms)';
|
||||
} else if (d.status === 'timeout') {
|
||||
dot.style.color = '#d97706';
|
||||
dot.title = 'Timeout (>6s)';
|
||||
} else {
|
||||
dot.style.color = '#dc2626';
|
||||
dot.title = 'Unreachable';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
dot.style.color = '#9ca3af';
|
||||
dot.title = 'Health check failed';
|
||||
});
|
||||
});
|
||||
/* ── Health dots — batched with max 4 concurrent requests ─── */
|
||||
(function() {
|
||||
var cards = Array.from(document.querySelectorAll('.site-card'));
|
||||
var CONCURRENCY = 4, idx = 0;
|
||||
|
||||
function probe() {
|
||||
if (idx >= cards.length) return;
|
||||
var card = cards[idx++];
|
||||
var id = card.dataset.id;
|
||||
var dot = document.getElementById('health-' + id);
|
||||
if (!dot) { probe(); return; }
|
||||
fetch('/dashboard/health/' + id, {credentials: 'same-origin'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.status === 'ok') {
|
||||
dot.style.color = d.ms > 3000 ? '#d97706' : '#16a34a';
|
||||
dot.title = 'Reachable (' + d.ms + 'ms)';
|
||||
} else if (d.status === 'timeout') {
|
||||
dot.style.color = '#d97706';
|
||||
dot.title = 'Timeout (>6s)';
|
||||
} else {
|
||||
dot.style.color = '#dc2626';
|
||||
dot.title = 'Unreachable';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
dot.style.color = '#9ca3af';
|
||||
dot.title = 'Health check failed';
|
||||
})
|
||||
.finally(probe);
|
||||
}
|
||||
|
||||
for (var i = 0; i < Math.min(CONCURRENCY, cards.length); i++) probe();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user