Defer hidden dashboard startup work

This commit is contained in:
James Smith
2026-03-19 09:19:36 +00:00
parent aaed831420
commit 5905aa6415
5 changed files with 146 additions and 98 deletions

View File

@@ -8,16 +8,41 @@ const AlertCenter = (function() {
let eventSource = null;
let reconnectTimer = null;
let lastConnectionWarningAt = 0;
let rulesLoaded = false;
let rulesPromise = null;
let bootTimer = null;
let feedLoaded = false;
function init() {
loadRules();
loadFeed();
connect();
function init(options = {}) {
const connectFeed = options.connectFeed !== false;
const refreshRules = options.refreshRules === true;
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() {
if (eventSource) {
eventSource.close();
return;
}
eventSource = new EventSource('/alerts/stream');
@@ -40,6 +65,10 @@ const AlertCenter = (function() {
lastConnectionWarningAt = now;
console.warn('[Alerts] SSE connection error; retrying');
}
if (eventSource) {
eventSource.close();
eventSource = null;
}
if (reconnectTimer) clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(connect, 2500);
};
@@ -133,6 +162,7 @@ const AlertCenter = (function() {
}
function loadFeed() {
feedLoaded = true;
fetch('/alerts/events?limit=30')
.then((r) => r.json())
.then((data) => {
@@ -144,21 +174,37 @@ const AlertCenter = (function() {
.catch((err) => console.error('[Alerts] Load feed failed', err));
}
function loadRules() {
return fetch('/alerts/rules?all=1')
function loadRules(force = false) {
if (!force && rulesLoaded) {
renderRulesUI();
return Promise.resolve(rules);
}
if (!force && rulesPromise) {
return rulesPromise;
}
rulesPromise = fetch('/alerts/rules?all=1')
.then((r) => r.json())
.then((data) => {
if (data.status === 'success') {
rules = data.rules || [];
rulesLoaded = true;
renderRulesUI();
}
return rules;
})
.catch((err) => {
console.error('[Alerts] Load rules failed', err);
if (typeof reportActionableError === 'function') {
reportActionableError('Alert Rules', err, { onRetry: loadRules });
}
throw err;
})
.finally(() => {
rulesPromise = null;
});
return rulesPromise;
}
function saveRule() {
@@ -260,7 +306,7 @@ const AlertCenter = (function() {
if (data.status !== 'success') {
throw new Error(data.message || 'Failed to update rule');
}
return loadRules();
return loadRules(true);
})
.catch((err) => {
if (typeof reportActionableError === 'function') {
@@ -287,7 +333,7 @@ const AlertCenter = (function() {
if (Number(getEditingRuleId()) === Number(ruleId)) {
clearRuleForm();
}
return loadRules();
return loadRules(true);
})
.catch((err) => {
if (typeof reportActionableError === 'function') {
@@ -325,7 +371,7 @@ const AlertCenter = (function() {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled }),
}).then(() => loadRules());
}).then(() => loadRules(true));
}
if (enabled) {
@@ -341,7 +387,7 @@ const AlertCenter = (function() {
enabled: true,
notify: { webhook: true },
}),
}).then(() => loadRules());
}).then(() => loadRules(true));
}
return null;
});
@@ -349,41 +395,63 @@ const AlertCenter = (function() {
function addBluetoothWatchlist(address, name) {
if (!address) return;
const upper = String(address).toUpperCase();
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
if (existing) return;
loadRules().then(() => {
const upper = String(address).toUpperCase();
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
if (existing) return;
fetch('/alerts/rules', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name ? `Watchlist ${name}` : `Watchlist ${upper}`,
mode: 'bluetooth',
event_type: 'device_update',
match: { address: upper },
severity: 'medium',
enabled: true,
notify: { webhook: true },
}),
}).then(() => loadRules());
return fetch('/alerts/rules', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name ? `Watchlist ${name}` : `Watchlist ${upper}`,
mode: 'bluetooth',
event_type: 'device_update',
match: { address: upper },
severity: 'medium',
enabled: true,
notify: { webhook: true },
}),
}).then(() => loadRules(true));
});
}
function removeBluetoothWatchlist(address) {
if (!address) return;
const upper = String(address).toUpperCase();
const existing = rules.find((r) => r.mode === 'bluetooth' && r.match && String(r.match.address || '').toUpperCase() === upper);
if (!existing) return;
loadRules().then(() => {
const upper = String(address).toUpperCase();
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' })
.then(() => loadRules());
return fetch(`/alerts/rules/${existing.id}`, { method: 'DELETE' })
.then(() => loadRules(true));
});
}
function isWatchlisted(address) {
if (!address) return false;
if (!rulesLoaded && !rulesPromise) {
loadRules();
}
const upper = String(address).toUpperCase();
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) {
if (!str) return '';
return String(str)
@@ -396,6 +464,7 @@ const AlertCenter = (function() {
return {
init,
scheduleInit,
loadFeed,
loadRules,
saveRule,
@@ -408,11 +477,12 @@ const AlertCenter = (function() {
addBluetoothWatchlist,
removeBluetoothWatchlist,
isWatchlisted,
destroy,
};
})();
document.addEventListener('DOMContentLoaded', () => {
if (typeof AlertCenter !== 'undefined') {
AlertCenter.init();
AlertCenter.scheduleInit();
}
});

View File

@@ -137,9 +137,3 @@ const RecordingUI = (function() {
openReplay,
};
})();
document.addEventListener('DOMContentLoaded', () => {
if (typeof RecordingUI !== 'undefined') {
RecordingUI.init();
}
});

View File

@@ -1292,11 +1292,11 @@ function switchSettingsTab(tabName) {
} else if (tabName === 'alerts') {
loadVoiceAlertConfig();
if (typeof AlertCenter !== 'undefined') {
AlertCenter.loadFeed();
AlertCenter.init();
}
} else if (tabName === 'recording') {
if (typeof RecordingUI !== 'undefined') {
RecordingUI.refresh();
RecordingUI.init();
}
} else if (tabName === 'apikeys') {
loadApiKeyStatus();

View File

@@ -2,12 +2,13 @@
* Updater Module - GitHub update checking and notification system
*/
const Updater = {
// State
_checkInterval: null,
_toastElement: null,
_modalElement: null,
_updateData: null,
const Updater = {
// State
_checkInterval: null,
_startupCheckTimer: null,
_toastElement: null,
_modalElement: null,
_updateData: null,
// Configuration
CHECK_INTERVAL_MS: 6 * 60 * 60 * 1000, // 6 hours in milliseconds
@@ -15,18 +16,31 @@ const Updater = {
/**
* Initialize the updater module
*/
init() {
// Create toast container if it doesn't exist
this._ensureToastContainer();
// Check for updates on page load
this.checkForUpdates();
// Set up periodic checks
this._checkInterval = setInterval(() => {
this.checkForUpdates();
}, this.CHECK_INTERVAL_MS);
},
init() {
// Create toast container if it doesn't exist
this._ensureToastContainer();
const enabled = localStorage.getItem('intercept_update_check_enabled') !== 'false';
if (!enabled) {
this.destroy();
return;
}
// 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
@@ -505,11 +519,15 @@ const Updater = {
/**
* Clean up on page unload
*/
destroy() {
if (this._checkInterval) {
clearInterval(this._checkInterval);
this._checkInterval = null;
}
destroy() {
if (this._startupCheckTimer) {
clearTimeout(this._startupCheckTimer);
this._startupCheckTimer = null;
}
if (this._checkInterval) {
clearInterval(this._checkInterval);
this._checkInterval = null;
}
this.hideToast();
this.hideModal();
}

View File

@@ -16241,40 +16241,6 @@
</div>
<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) {
const notice = document.createElement('div');
notice.id = 'startupDepsModal';