mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Defer hidden dashboard startup work
This commit is contained in:
@@ -8,16 +8,41 @@ const AlertCenter = (function() {
|
|||||||
let eventSource = null;
|
let eventSource = null;
|
||||||
let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
let lastConnectionWarningAt = 0;
|
let lastConnectionWarningAt = 0;
|
||||||
|
let rulesLoaded = false;
|
||||||
|
let rulesPromise = null;
|
||||||
|
let bootTimer = null;
|
||||||
|
let feedLoaded = false;
|
||||||
|
|
||||||
function init() {
|
function init(options = {}) {
|
||||||
loadRules();
|
const connectFeed = options.connectFeed !== false;
|
||||||
loadFeed();
|
const refreshRules = options.refreshRules === true;
|
||||||
connect();
|
|
||||||
|
if (bootTimer) {
|
||||||
|
clearTimeout(bootTimer);
|
||||||
|
bootTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRules(refreshRules);
|
||||||
|
|
||||||
|
if (connectFeed) {
|
||||||
|
if (!feedLoaded) {
|
||||||
|
loadFeed();
|
||||||
|
}
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleInit(delayMs = 15000) {
|
||||||
|
if (bootTimer || eventSource) return;
|
||||||
|
bootTimer = window.setTimeout(() => {
|
||||||
|
bootTimer = null;
|
||||||
|
init();
|
||||||
|
}, delayMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
if (eventSource) {
|
if (eventSource) {
|
||||||
eventSource.close();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
eventSource = new EventSource('/alerts/stream');
|
eventSource = new EventSource('/alerts/stream');
|
||||||
@@ -40,6 +65,10 @@ const AlertCenter = (function() {
|
|||||||
lastConnectionWarningAt = now;
|
lastConnectionWarningAt = now;
|
||||||
console.warn('[Alerts] SSE connection error; retrying');
|
console.warn('[Alerts] SSE connection error; retrying');
|
||||||
}
|
}
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close();
|
||||||
|
eventSource = null;
|
||||||
|
}
|
||||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||||
reconnectTimer = setTimeout(connect, 2500);
|
reconnectTimer = setTimeout(connect, 2500);
|
||||||
};
|
};
|
||||||
@@ -133,6 +162,7 @@ const AlertCenter = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadFeed() {
|
function loadFeed() {
|
||||||
|
feedLoaded = true;
|
||||||
fetch('/alerts/events?limit=30')
|
fetch('/alerts/events?limit=30')
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -144,21 +174,37 @@ const AlertCenter = (function() {
|
|||||||
.catch((err) => console.error('[Alerts] Load feed failed', err));
|
.catch((err) => console.error('[Alerts] Load feed failed', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadRules() {
|
function loadRules(force = false) {
|
||||||
return fetch('/alerts/rules?all=1')
|
if (!force && rulesLoaded) {
|
||||||
|
renderRulesUI();
|
||||||
|
return Promise.resolve(rules);
|
||||||
|
}
|
||||||
|
if (!force && rulesPromise) {
|
||||||
|
return rulesPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
rulesPromise = fetch('/alerts/rules?all=1')
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
rules = data.rules || [];
|
rules = data.rules || [];
|
||||||
|
rulesLoaded = true;
|
||||||
renderRulesUI();
|
renderRulesUI();
|
||||||
}
|
}
|
||||||
|
return rules;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('[Alerts] Load rules failed', err);
|
console.error('[Alerts] Load rules failed', err);
|
||||||
if (typeof reportActionableError === 'function') {
|
if (typeof reportActionableError === 'function') {
|
||||||
reportActionableError('Alert Rules', err, { onRetry: loadRules });
|
reportActionableError('Alert Rules', err, { onRetry: loadRules });
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
rulesPromise = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return rulesPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveRule() {
|
function saveRule() {
|
||||||
@@ -260,7 +306,7 @@ const AlertCenter = (function() {
|
|||||||
if (data.status !== 'success') {
|
if (data.status !== 'success') {
|
||||||
throw new Error(data.message || 'Failed to update rule');
|
throw new Error(data.message || 'Failed to update rule');
|
||||||
}
|
}
|
||||||
return loadRules();
|
return loadRules(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (typeof reportActionableError === 'function') {
|
if (typeof reportActionableError === 'function') {
|
||||||
@@ -287,7 +333,7 @@ const AlertCenter = (function() {
|
|||||||
if (Number(getEditingRuleId()) === Number(ruleId)) {
|
if (Number(getEditingRuleId()) === Number(ruleId)) {
|
||||||
clearRuleForm();
|
clearRuleForm();
|
||||||
}
|
}
|
||||||
return loadRules();
|
return loadRules(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (typeof reportActionableError === 'function') {
|
if (typeof reportActionableError === 'function') {
|
||||||
@@ -325,7 +371,7 @@ const AlertCenter = (function() {
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ enabled }),
|
body: JSON.stringify({ enabled }),
|
||||||
}).then(() => loadRules());
|
}).then(() => loadRules(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@@ -341,7 +387,7 @@ const AlertCenter = (function() {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
notify: { webhook: true },
|
notify: { webhook: true },
|
||||||
}),
|
}),
|
||||||
}).then(() => loadRules());
|
}).then(() => loadRules(true));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@@ -349,41 +395,63 @@ const AlertCenter = (function() {
|
|||||||
|
|
||||||
function addBluetoothWatchlist(address, name) {
|
function addBluetoothWatchlist(address, name) {
|
||||||
if (!address) return;
|
if (!address) return;
|
||||||
const upper = String(address).toUpperCase();
|
loadRules().then(() => {
|
||||||
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
|
const upper = String(address).toUpperCase();
|
||||||
if (existing) return;
|
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
|
||||||
|
if (existing) return;
|
||||||
|
|
||||||
fetch('/alerts/rules', {
|
return fetch('/alerts/rules', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: name ? `Watchlist ${name}` : `Watchlist ${upper}`,
|
name: name ? `Watchlist ${name}` : `Watchlist ${upper}`,
|
||||||
mode: 'bluetooth',
|
mode: 'bluetooth',
|
||||||
event_type: 'device_update',
|
event_type: 'device_update',
|
||||||
match: { address: upper },
|
match: { address: upper },
|
||||||
severity: 'medium',
|
severity: 'medium',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
notify: { webhook: true },
|
notify: { webhook: true },
|
||||||
}),
|
}),
|
||||||
}).then(() => loadRules());
|
}).then(() => loadRules(true));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeBluetoothWatchlist(address) {
|
function removeBluetoothWatchlist(address) {
|
||||||
if (!address) return;
|
if (!address) return;
|
||||||
const upper = String(address).toUpperCase();
|
loadRules().then(() => {
|
||||||
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
|
const upper = String(address).toUpperCase();
|
||||||
if (!existing) return;
|
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
|
||||||
|
if (!existing) return;
|
||||||
|
|
||||||
fetch(`/alerts/rules/${existing.id}`, { method: 'DELETE' })
|
return fetch(`/alerts/rules/${existing.id}`, { method: 'DELETE' })
|
||||||
.then(() => loadRules());
|
.then(() => loadRules(true));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWatchlisted(address) {
|
function isWatchlisted(address) {
|
||||||
if (!address) return false;
|
if (!address) return false;
|
||||||
|
if (!rulesLoaded && !rulesPromise) {
|
||||||
|
loadRules();
|
||||||
|
}
|
||||||
const upper = String(address).toUpperCase();
|
const upper = String(address).toUpperCase();
|
||||||
return rules.some((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper && r.enabled);
|
return rules.some((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper && r.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if (bootTimer) {
|
||||||
|
clearTimeout(bootTimer);
|
||||||
|
bootTimer = null;
|
||||||
|
}
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close();
|
||||||
|
eventSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
return String(str)
|
return String(str)
|
||||||
@@ -396,6 +464,7 @@ const AlertCenter = (function() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
init,
|
init,
|
||||||
|
scheduleInit,
|
||||||
loadFeed,
|
loadFeed,
|
||||||
loadRules,
|
loadRules,
|
||||||
saveRule,
|
saveRule,
|
||||||
@@ -408,11 +477,12 @@ const AlertCenter = (function() {
|
|||||||
addBluetoothWatchlist,
|
addBluetoothWatchlist,
|
||||||
removeBluetoothWatchlist,
|
removeBluetoothWatchlist,
|
||||||
isWatchlisted,
|
isWatchlisted,
|
||||||
|
destroy,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (typeof AlertCenter !== 'undefined') {
|
if (typeof AlertCenter !== 'undefined') {
|
||||||
AlertCenter.init();
|
AlertCenter.scheduleInit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -137,9 +137,3 @@ const RecordingUI = (function() {
|
|||||||
openReplay,
|
openReplay,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
if (typeof RecordingUI !== 'undefined') {
|
|
||||||
RecordingUI.init();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1292,11 +1292,11 @@ function switchSettingsTab(tabName) {
|
|||||||
} else if (tabName === 'alerts') {
|
} else if (tabName === 'alerts') {
|
||||||
loadVoiceAlertConfig();
|
loadVoiceAlertConfig();
|
||||||
if (typeof AlertCenter !== 'undefined') {
|
if (typeof AlertCenter !== 'undefined') {
|
||||||
AlertCenter.loadFeed();
|
AlertCenter.init();
|
||||||
}
|
}
|
||||||
} else if (tabName === 'recording') {
|
} else if (tabName === 'recording') {
|
||||||
if (typeof RecordingUI !== 'undefined') {
|
if (typeof RecordingUI !== 'undefined') {
|
||||||
RecordingUI.refresh();
|
RecordingUI.init();
|
||||||
}
|
}
|
||||||
} else if (tabName === 'apikeys') {
|
} else if (tabName === 'apikeys') {
|
||||||
loadApiKeyStatus();
|
loadApiKeyStatus();
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
* Updater Module - GitHub update checking and notification system
|
* Updater Module - GitHub update checking and notification system
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Updater = {
|
const Updater = {
|
||||||
// State
|
// State
|
||||||
_checkInterval: null,
|
_checkInterval: null,
|
||||||
_toastElement: null,
|
_startupCheckTimer: null,
|
||||||
_modalElement: null,
|
_toastElement: null,
|
||||||
_updateData: null,
|
_modalElement: null,
|
||||||
|
_updateData: null,
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
CHECK_INTERVAL_MS: 6 * 60 * 60 * 1000, // 6 hours in milliseconds
|
CHECK_INTERVAL_MS: 6 * 60 * 60 * 1000, // 6 hours in milliseconds
|
||||||
@@ -15,18 +16,31 @@ const Updater = {
|
|||||||
/**
|
/**
|
||||||
* Initialize the updater module
|
* Initialize the updater module
|
||||||
*/
|
*/
|
||||||
init() {
|
init() {
|
||||||
// Create toast container if it doesn't exist
|
// Create toast container if it doesn't exist
|
||||||
this._ensureToastContainer();
|
this._ensureToastContainer();
|
||||||
|
|
||||||
// Check for updates on page load
|
const enabled = localStorage.getItem('intercept_update_check_enabled') !== 'false';
|
||||||
this.checkForUpdates();
|
if (!enabled) {
|
||||||
|
this.destroy();
|
||||||
// Set up periodic checks
|
return;
|
||||||
this._checkInterval = setInterval(() => {
|
}
|
||||||
this.checkForUpdates();
|
|
||||||
}, this.CHECK_INTERVAL_MS);
|
// Defer the first check so the active dashboard can finish loading first.
|
||||||
},
|
if (!this._startupCheckTimer) {
|
||||||
|
this._startupCheckTimer = setTimeout(() => {
|
||||||
|
this._startupCheckTimer = null;
|
||||||
|
this.checkForUpdates();
|
||||||
|
}, 15000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up periodic checks
|
||||||
|
if (!this._checkInterval) {
|
||||||
|
this._checkInterval = setInterval(() => {
|
||||||
|
this.checkForUpdates();
|
||||||
|
}, this.CHECK_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure toast container exists in DOM
|
* Ensure toast container exists in DOM
|
||||||
@@ -505,11 +519,15 @@ const Updater = {
|
|||||||
/**
|
/**
|
||||||
* Clean up on page unload
|
* Clean up on page unload
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this._checkInterval) {
|
if (this._startupCheckTimer) {
|
||||||
clearInterval(this._checkInterval);
|
clearTimeout(this._startupCheckTimer);
|
||||||
this._checkInterval = null;
|
this._startupCheckTimer = null;
|
||||||
}
|
}
|
||||||
|
if (this._checkInterval) {
|
||||||
|
clearInterval(this._checkInterval);
|
||||||
|
this._checkInterval = null;
|
||||||
|
}
|
||||||
this.hideToast();
|
this.hideToast();
|
||||||
this.hideModal();
|
this.hideModal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16241,40 +16241,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Check dependencies on page load
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
// Check if user dismissed the startup check
|
|
||||||
const dismissed = localStorage.getItem('depsCheckDismissed');
|
|
||||||
|
|
||||||
// Quick check for missing dependencies
|
|
||||||
fetch('/dependencies')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'success') {
|
|
||||||
let missingModes = 0;
|
|
||||||
let missingTools = [];
|
|
||||||
|
|
||||||
for (const [modeKey, mode] of Object.entries(data.modes)) {
|
|
||||||
if (!mode.ready) {
|
|
||||||
missingModes++;
|
|
||||||
mode.missing_required.forEach(tool => {
|
|
||||||
if (!missingTools.includes(tool)) {
|
|
||||||
missingTools.push(tool);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show startup prompt if tools are missing and not dismissed
|
|
||||||
// Only show if disclaimer has been accepted
|
|
||||||
const disclaimerAccepted = localStorage.getItem('disclaimerAccepted') === 'true';
|
|
||||||
if (missingModes > 0 && !dismissed && disclaimerAccepted) {
|
|
||||||
showStartupDepsPrompt(missingModes, missingTools.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function showStartupDepsPrompt(modeCount, toolCount) {
|
function showStartupDepsPrompt(modeCount, toolCount) {
|
||||||
const notice = document.createElement('div');
|
const notice = document.createElement('div');
|
||||||
notice.id = 'startupDepsModal';
|
notice.id = 'startupDepsModal';
|
||||||
|
|||||||
Reference in New Issue
Block a user