@@ -238,6 +238,21 @@
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
}
+ function timeAgo(iso) {
+ if (!iso) return '-';
+ const d = new Date(iso);
+ const now = new Date();
+ const diffMs = now - d;
+ const mins = Math.floor(diffMs / 60000);
+ if (mins < 1) return 'just now';
+ if (mins < 60) return `${mins}m ago`;
+ const hrs = Math.floor(mins / 60);
+ const remMins = mins % 60;
+ if (hrs < 24) return remMins > 0 ? `${hrs}h ${remMins}m ago` : `${hrs}h ago`;
+ const days = Math.floor(hrs / 24);
+ return `${days}d ago`;
+ }
+
function getNotificationStatus(row) {
if (row.dismissed) return { class: 'status-dismissed', text: 'Dismissed' };
if (row.acted_at) return { class: 'status-acted', text: 'Acted' };
@@ -263,25 +278,42 @@
"SELECT COUNT(*) as c FROM escalations WHERE status = 'open'");
document.getElementById('escalation-count').textContent = escalations[0]?.c || 0;
- // Notifications table
+ // Last poll info
+ const polls = await fetchData('notification_polls',
+ 'SELECT polled_at, new_count FROM notification_polls ORDER BY polled_at DESC LIMIT 1');
+ if (polls.length > 0) {
+ const p = polls[0];
+ document.getElementById('poll-info').textContent =
+ `(last poll ${timeAgo(p.polled_at)} — ${p.new_count} new)`;
+ }
+
+ // Notifications table — show pending (non-dismissed) by default
const notifications = await fetchData('notifications',
- 'SELECT * FROM notifications ORDER BY seen_at DESC LIMIT 10');
+ 'SELECT * FROM notifications WHERE dismissed = 0 ORDER BY seen_at DESC LIMIT 30');
if (notifications.length === 0) {
document.getElementById('notifications-table').innerHTML =
- '
No notifications yet.
';
+ '
No pending notifications. ✨
';
} else {
let html = `
- | Status | Title | Repo | Seen |
+ | TIME | ISSUE | REASON | TITLE | FILTER |
`;
for (const n of notifications) {
- const status = getNotificationStatus(n);
- const title = n.url ? `${n.title}` : n.title;
+ const ownTag = n.is_own_pr ? '🔵 ' : '';
+ const ciTag = n.ci_status ? ` [CI:${n.ci_status}]` : '';
+ const repo_short = n.repo_id ? n.repo_id.split('/').pop() : '-';
+ const issue = n.url ? `${repo_short}` : repo_short;
+ const reasonClass = n.reason === 'mention' ? 'accent' :
+ n.reason === 'author' ? 'success' :
+ n.reason === 'review_requested' ? 'warning' : '';
+ const reasonBadge = `${n.reason}`;
+ const filterShort = (n.filter_reason || '').replace(/^KEEP: /, '');
html += `
- | ${status.text} |
- ${title} |
- ${n.repo_id || '-'} |
- ${formatDate(n.seen_at)} |
+ ${timeAgo(n.seen_at)} |
+ ${issue} |
+ ${reasonBadge} |
+ ${ownTag}${n.title || '-'}${ciTag} |
+ ${filterShort} |
`;
}
html += '
';