mirror of
https://github.com/smittix/intercept.git
synced 2026-04-25 07:10:00 -07:00
Add alerts/recording, WiFi/TSCM updates, optimize waterfall
This commit is contained in:
194
static/js/core/alerts.js
Normal file
194
static/js/core/alerts.js
Normal file
@@ -0,0 +1,194 @@
|
||||
const AlertCenter = (function() {
|
||||
'use strict';
|
||||
|
||||
let alerts = [];
|
||||
let rules = [];
|
||||
let eventSource = null;
|
||||
|
||||
const TRACKER_RULE_NAME = 'Tracker Detected';
|
||||
|
||||
function init() {
|
||||
loadRules();
|
||||
loadFeed();
|
||||
connect();
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
}
|
||||
eventSource = new EventSource('/alerts/stream');
|
||||
eventSource.onmessage = function(e) {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'keepalive') return;
|
||||
handleAlert(data);
|
||||
} catch (err) {
|
||||
console.error('[Alerts] SSE parse error', err);
|
||||
}
|
||||
};
|
||||
eventSource.onerror = function() {
|
||||
console.warn('[Alerts] SSE connection error');
|
||||
};
|
||||
}
|
||||
|
||||
function handleAlert(alert) {
|
||||
alerts.unshift(alert);
|
||||
alerts = alerts.slice(0, 50);
|
||||
updateFeedUI();
|
||||
|
||||
if (typeof showNotification === 'function') {
|
||||
const severity = (alert.severity || '').toLowerCase();
|
||||
if (['high', 'critical'].includes(severity)) {
|
||||
showNotification(alert.title || 'Alert', alert.message || 'Alert triggered');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateFeedUI() {
|
||||
const list = document.getElementById('alertsFeedList');
|
||||
const countEl = document.getElementById('alertsFeedCount');
|
||||
if (countEl) countEl.textContent = `(${alerts.length})`;
|
||||
if (!list) return;
|
||||
|
||||
if (alerts.length === 0) {
|
||||
list.innerHTML = '<div class="settings-feed-empty">No alerts yet</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = alerts.map(alert => {
|
||||
const title = escapeHtml(alert.title || 'Alert');
|
||||
const message = escapeHtml(alert.message || '');
|
||||
const severity = escapeHtml(alert.severity || 'medium');
|
||||
const createdAt = alert.created_at ? new Date(alert.created_at).toLocaleString() : '';
|
||||
return `
|
||||
<div class="settings-feed-item">
|
||||
<div class="settings-feed-title">
|
||||
<span>${title}</span>
|
||||
<span style="color: var(--text-dim);">${severity.toUpperCase()}</span>
|
||||
</div>
|
||||
<div class="settings-feed-meta">${message}</div>
|
||||
<div class="settings-feed-meta" style="margin-top: 4px;">${createdAt}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function loadFeed() {
|
||||
fetch('/alerts/events?limit=20')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
alerts = data.events || [];
|
||||
updateFeedUI();
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('[Alerts] Load feed failed', err));
|
||||
}
|
||||
|
||||
function loadRules() {
|
||||
fetch('/alerts/rules?all=1')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
rules = data.rules || [];
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('[Alerts] Load rules failed', err));
|
||||
}
|
||||
|
||||
function enableTrackerAlerts() {
|
||||
ensureTrackerRule(true);
|
||||
}
|
||||
|
||||
function disableTrackerAlerts() {
|
||||
ensureTrackerRule(false);
|
||||
}
|
||||
|
||||
function ensureTrackerRule(enabled) {
|
||||
loadRules();
|
||||
setTimeout(() => {
|
||||
const existing = rules.find(r => r.name === TRACKER_RULE_NAME);
|
||||
if (existing) {
|
||||
fetch(`/alerts/rules/${existing.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled })
|
||||
}).then(() => loadRules());
|
||||
} else if (enabled) {
|
||||
fetch('/alerts/rules', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: TRACKER_RULE_NAME,
|
||||
mode: 'bluetooth',
|
||||
event_type: 'device_update',
|
||||
match: { is_tracker: true },
|
||||
severity: 'high',
|
||||
enabled: true,
|
||||
notify: { webhook: true }
|
||||
})
|
||||
}).then(() => loadRules());
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function addBluetoothWatchlist(address, name) {
|
||||
if (!address) return;
|
||||
const existing = rules.find(r => r.mode === 'bluetooth' && r.match && r.match.address === address);
|
||||
if (existing) {
|
||||
return;
|
||||
}
|
||||
fetch('/alerts/rules', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: name ? `Watchlist ${name}` : `Watchlist ${address}`,
|
||||
mode: 'bluetooth',
|
||||
event_type: 'device_update',
|
||||
match: { address: address },
|
||||
severity: 'medium',
|
||||
enabled: true,
|
||||
notify: { webhook: true }
|
||||
})
|
||||
}).then(() => loadRules());
|
||||
}
|
||||
|
||||
function removeBluetoothWatchlist(address) {
|
||||
if (!address) return;
|
||||
const existing = rules.find(r => r.mode === 'bluetooth' && r.match && r.match.address === address);
|
||||
if (!existing) return;
|
||||
fetch(`/alerts/rules/${existing.id}`, { method: 'DELETE' })
|
||||
.then(() => loadRules());
|
||||
}
|
||||
|
||||
function isWatchlisted(address) {
|
||||
return rules.some(r => r.mode === 'bluetooth' && r.match && r.match.address === address && r.enabled);
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
return {
|
||||
init,
|
||||
loadFeed,
|
||||
enableTrackerAlerts,
|
||||
disableTrackerAlerts,
|
||||
addBluetoothWatchlist,
|
||||
removeBluetoothWatchlist,
|
||||
isWatchlisted,
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof AlertCenter !== 'undefined') {
|
||||
AlertCenter.init();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user