169 lines
6.6 KiB
JavaScript
169 lines
6.6 KiB
JavaScript
/* ─── RADAR: Live Intelligence Feed ─────────────── */
|
|
(function() {
|
|
'use strict';
|
|
|
|
const API = '/api/radar';
|
|
const REFRESH_INTERVAL = 15 * 60 * 1000; // 15 minutes
|
|
let currentSource = 'all';
|
|
let allItems = [];
|
|
let refreshTimer = null;
|
|
|
|
// ─── Time Ago ──────────────────────────────────
|
|
function timeAgo(dateStr) {
|
|
if (!dateStr) return '';
|
|
const now = new Date();
|
|
const then = new Date(dateStr);
|
|
const diff = Math.floor((now - then) / 1000);
|
|
if (diff < 60) return diff + 's ago';
|
|
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
|
|
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
|
|
if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
|
|
return Math.floor(diff / 604800) + 'w ago';
|
|
}
|
|
|
|
// ─── Extract Domain ────────────────────────────
|
|
function extractDomain(url) {
|
|
try {
|
|
const u = new URL(url);
|
|
return u.hostname.replace('www.', '');
|
|
} catch(e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// ─── Escape HTML ───────────────────────────────
|
|
function esc(s) {
|
|
const d = document.createElement('div');
|
|
d.textContent = s;
|
|
return d.innerHTML;
|
|
}
|
|
|
|
// ─── Render Feed ───────────────────────────────
|
|
function renderFeed(items) {
|
|
const feed = document.getElementById('radarFeed');
|
|
if (!items || items.length === 0) {
|
|
feed.innerHTML = '<div class="radar-empty">NO SIGNALS DETECTED ON CURRENT FREQUENCY</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
items.forEach(item => {
|
|
const domain = extractDomain(item.url);
|
|
const ago = timeAgo(item.published);
|
|
const sourceClass = 'source-' + item.source_id;
|
|
const sourceLabel = item.source || 'UNKNOWN';
|
|
|
|
html += '<div class="radar-item">';
|
|
html += ' <div class="radar-item-time">' + esc(ago) + '</div>';
|
|
html += ' <div class="radar-item-source ' + sourceClass + '">' + esc(sourceLabel) + '</div>';
|
|
html += ' <div class="radar-item-content">';
|
|
html += ' <a href="' + esc(item.url) + '" class="radar-item-title" target="_blank" rel="noopener">' + esc(item.title) + '</a>';
|
|
html += ' <div class="radar-item-meta">';
|
|
if (domain) {
|
|
html += ' <span class="radar-item-domain">' + esc(domain) + '</span>';
|
|
}
|
|
if (item.comments_url) {
|
|
html += ' <a href="' + esc(item.comments_url) + '" class="radar-item-comments" target="_blank" rel="noopener">COMMENTS</a>';
|
|
}
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
html += '</div>';
|
|
});
|
|
|
|
feed.innerHTML = html;
|
|
}
|
|
|
|
// ─── Filter & Search ───────────────────────────
|
|
function applyFilters() {
|
|
const q = document.getElementById('radarSearch').value.trim().toLowerCase();
|
|
let filtered = allItems;
|
|
|
|
if (currentSource !== 'all') {
|
|
filtered = filtered.filter(i => i.source_id === currentSource);
|
|
}
|
|
if (q) {
|
|
filtered = filtered.filter(i =>
|
|
(i.title || '').toLowerCase().includes(q) ||
|
|
(i.summary || '').toLowerCase().includes(q)
|
|
);
|
|
}
|
|
|
|
document.getElementById('statTotal').textContent = filtered.length;
|
|
renderFeed(filtered);
|
|
}
|
|
|
|
// ─── Fetch Data ────────────────────────────────
|
|
async function fetchRadar(forceRefresh) {
|
|
const feed = document.getElementById('radarFeed');
|
|
feed.innerHTML = '<div class="radar-loading">SCANNING FREQUENCIES...</div>';
|
|
|
|
try {
|
|
if (forceRefresh) {
|
|
await fetch(API + '/refresh', { method: 'POST' });
|
|
}
|
|
|
|
const res = await fetch(API);
|
|
const data = await res.json();
|
|
|
|
allItems = data.items || [];
|
|
document.getElementById('statTotal').textContent = allItems.length;
|
|
|
|
// Format last updated
|
|
if (data.last_updated) {
|
|
const d = new Date(data.last_updated);
|
|
const pad = n => String(n).padStart(2, '0');
|
|
document.getElementById('statUpdated').textContent =
|
|
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + ' UTC';
|
|
}
|
|
|
|
applyFilters();
|
|
|
|
} catch(err) {
|
|
console.error('RADAR fetch error:', err);
|
|
feed.innerHTML = '<div class="radar-empty">⚠ SIGNAL LOST — UNABLE TO REACH FEED API</div>';
|
|
}
|
|
}
|
|
|
|
// ─── Event Listeners ───────────────────────────
|
|
function init() {
|
|
// Source filters
|
|
document.querySelectorAll('.radar-filter').forEach(btn => {
|
|
btn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
document.querySelectorAll('.radar-filter').forEach(b => b.classList.remove('active'));
|
|
this.classList.add('active');
|
|
currentSource = this.dataset.source;
|
|
applyFilters();
|
|
});
|
|
});
|
|
|
|
// Search
|
|
let searchTimeout;
|
|
document.getElementById('radarSearch').addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(applyFilters, 200);
|
|
});
|
|
|
|
// Refresh button
|
|
document.getElementById('radarRefresh').addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
this.classList.add('spinning');
|
|
fetchRadar(true).then(() => {
|
|
setTimeout(() => this.classList.remove('spinning'), 500);
|
|
});
|
|
});
|
|
|
|
// Initial fetch
|
|
fetchRadar(false);
|
|
|
|
// Auto-refresh every 15 min
|
|
refreshTimer = setInterval(() => fetchRadar(false), REFRESH_INTERVAL);
|
|
}
|
|
|
|
// ─── Boot ──────────────────────────────────────
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|