mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-05-09 06:44:41 +02:00
fix: prevent page hang when device channel queries block
/api/messages and /api/messages/updates called get_channels_cached() which blocks on device communication when cache is cold (up to 240s). Now uses DB-cached channels for pkt_payload computation instead. Frontend loadMessages() now has a 15s timeout with auto-retry and clears the loading spinner on error instead of leaving it spinning indefinitely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+23
-22
@@ -371,14 +371,15 @@ def get_messages():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Build channel secret lookup for pkt_payload computation
|
# Build channel secret lookup for pkt_payload computation
|
||||||
|
# Use DB channels (fast) instead of get_channels_cached() which
|
||||||
|
# can block on device communication when cache is cold
|
||||||
channel_secrets = {}
|
channel_secrets = {}
|
||||||
_, channels_list = get_channels_cached()
|
db_channels = db.get_channels() if db else []
|
||||||
if channels_list:
|
for ch_info in db_channels:
|
||||||
for ch_info in channels_list:
|
ch_key = ch_info.get('secret', ch_info.get('key', ''))
|
||||||
ch_key = ch_info.get('key', '')
|
ch_idx = ch_info.get('idx', ch_info.get('index'))
|
||||||
ch_idx = ch_info.get('index')
|
if ch_key and ch_idx is not None:
|
||||||
if ch_key and ch_idx is not None:
|
channel_secrets[ch_idx] = ch_key
|
||||||
channel_secrets[ch_idx] = ch_key
|
|
||||||
|
|
||||||
# Convert DB rows to frontend-compatible format
|
# Convert DB rows to frontend-compatible format
|
||||||
messages = []
|
messages = []
|
||||||
@@ -485,15 +486,15 @@ def get_message_meta(msg_id):
|
|||||||
txt_type = row.get('txt_type', 0)
|
txt_type = row.get('txt_type', 0)
|
||||||
|
|
||||||
# Compute pkt_payload if not stored
|
# Compute pkt_payload if not stored
|
||||||
|
# Use DB channels (fast) to avoid blocking on device communication
|
||||||
if not pkt_payload and sender_ts:
|
if not pkt_payload and sender_ts:
|
||||||
_, channels_list = get_channels_cached()
|
db_channels = db.get_channels() if db else []
|
||||||
channel_secrets = {}
|
channel_secrets = {}
|
||||||
if channels_list:
|
for ch_info in db_channels:
|
||||||
for ch_info in channels_list:
|
ch_key = ch_info.get('secret', ch_info.get('key', ''))
|
||||||
ch_key = ch_info.get('key', '')
|
ci = ch_info.get('idx', ch_info.get('index'))
|
||||||
ci = ch_info.get('index')
|
if ch_key and ci is not None:
|
||||||
if ch_key and ci is not None:
|
channel_secrets[ci] = ch_key
|
||||||
channel_secrets[ci] = ch_key
|
|
||||||
|
|
||||||
if ch_idx in channel_secrets:
|
if ch_idx in channel_secrets:
|
||||||
raw_text = None
|
raw_text = None
|
||||||
@@ -1698,17 +1699,17 @@ def get_messages_updates():
|
|||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
last_seen = {}
|
last_seen = {}
|
||||||
|
|
||||||
# Get list of channels (cached)
|
# Get list of channels from DB (fast) to avoid blocking on device
|
||||||
success_ch, channels = get_channels_cached()
|
db = _get_db()
|
||||||
if not success_ch:
|
db_channels = db.get_channels() if db else []
|
||||||
return jsonify({
|
# Normalize DB channel format to match expected structure
|
||||||
'success': False,
|
channels = [{'index': ch['idx'], 'name': ch['name']} for ch in db_channels]
|
||||||
'error': 'Failed to get channels'
|
if not channels:
|
||||||
}), 500
|
# Fallback: at least include Public channel
|
||||||
|
channels = [{'index': 0, 'name': 'Public'}]
|
||||||
|
|
||||||
# OPTIMIZATION: Read ALL messages ONCE (no channel filter)
|
# OPTIMIZATION: Read ALL messages ONCE (no channel filter)
|
||||||
# Then compute per-channel statistics in memory
|
# Then compute per-channel statistics in memory
|
||||||
db = _get_db()
|
|
||||||
if db:
|
if db:
|
||||||
all_messages = db.get_channel_messages(limit=None, days=7)
|
all_messages = db.get_channel_messages(limit=None, days=7)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+27
-2
@@ -854,7 +854,12 @@ async function loadMessages() {
|
|||||||
url += '&days=7';
|
url += '&days=7';
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url);
|
// Add timeout to prevent hanging spinner
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 15000);
|
||||||
|
|
||||||
|
const response = await fetch(url, { signal: controller.signal });
|
||||||
|
clearTimeout(timeoutId);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@@ -863,11 +868,31 @@ async function loadMessages() {
|
|||||||
updateLastRefresh();
|
updateLastRefresh();
|
||||||
} else {
|
} else {
|
||||||
showNotification('Error loading messages: ' + data.error, 'danger');
|
showNotification('Error loading messages: ' + data.error, 'danger');
|
||||||
|
clearLoadingSpinner();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading messages:', error);
|
console.error('Error loading messages:', error);
|
||||||
updateStatus('disconnected');
|
updateStatus('disconnected');
|
||||||
showNotification('Failed to load messages', 'danger');
|
clearLoadingSpinner();
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
showNotification('Loading messages timed out — retrying...', 'warning');
|
||||||
|
setTimeout(loadMessages, 2000);
|
||||||
|
} else {
|
||||||
|
showNotification('Failed to load messages', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLoadingSpinner() {
|
||||||
|
const container = document.getElementById('messagesList');
|
||||||
|
if (container && container.querySelector('.spinner-border')) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i>
|
||||||
|
<p>Could not load messages</p>
|
||||||
|
<small>Will retry automatically</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user