mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
fix(dm): show delivery route as hex path, add real-time delivery info
Store actual hex path instead of DIRECT/FLOOD labels in delivery_path. Format route as AB→CD→EF (same as channel messages, truncated if >4 hops). Add dm_delivered_info WebSocket event so delivery meta appears in real-time without needing page reload. Remove path info from failed messages since it's not meaningful for undelivered messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1355,8 +1355,17 @@ class DeviceManager:
|
|||||||
if not no_auto_flood:
|
if not no_auto_flood:
|
||||||
max_attempts += cfg['flood_max_retries']
|
max_attempts += cfg['flood_max_retries']
|
||||||
|
|
||||||
# Track current path description for delivery info
|
# Track current path hex for delivery info (actual route, not label)
|
||||||
path_desc = "FLOOD" if not has_path else "DIRECT"
|
def _extract_path_hex(out_path, out_path_len):
|
||||||
|
"""Extract meaningful hex portion from device path."""
|
||||||
|
if out_path_len <= 0 or not out_path:
|
||||||
|
return ''
|
||||||
|
hop_count = out_path_len & 0x3F
|
||||||
|
hash_size = (out_path_len >> 6) + 1
|
||||||
|
meaningful_len = hop_count * hash_size * 2
|
||||||
|
return out_path[:meaningful_len].lower() if meaningful_len > 0 else ''
|
||||||
|
|
||||||
|
path_desc = _extract_path_hex(original_out_path, original_out_path_len) if has_path else ''
|
||||||
|
|
||||||
logger.info(f"DM retry task started: dm_id={dm_id}, scenario={scenario}, "
|
logger.info(f"DM retry task started: dm_id={dm_id}, scenario={scenario}, "
|
||||||
f"configured_paths={len(configured_paths)}, no_auto_flood={no_auto_flood}, "
|
f"configured_paths={len(configured_paths)}, no_auto_flood={no_auto_flood}, "
|
||||||
@@ -1373,6 +1382,13 @@ class DeviceManager:
|
|||||||
if delivered:
|
if delivered:
|
||||||
self.db.update_dm_delivery_info(
|
self.db.update_dm_delivery_info(
|
||||||
dm_id, display, max_attempts, path_desc)
|
dm_id, display, max_attempts, path_desc)
|
||||||
|
if self.socketio:
|
||||||
|
self.socketio.emit('dm_delivered_info', {
|
||||||
|
'dm_id': dm_id,
|
||||||
|
'attempt': display,
|
||||||
|
'max_attempts': max_attempts,
|
||||||
|
'path': path_desc,
|
||||||
|
}, namespace='/chat')
|
||||||
return delivered
|
return delivered
|
||||||
|
|
||||||
# ── Emit status for initial send (attempt 1) and wait for ACK ──
|
# ── Emit status for initial send (attempt 1) and wait for ACK ──
|
||||||
@@ -1387,6 +1403,11 @@ class DeviceManager:
|
|||||||
if ack_event:
|
if ack_event:
|
||||||
self._confirm_delivery(dm_id, initial_ack, ack_event)
|
self._confirm_delivery(dm_id, initial_ack, ack_event)
|
||||||
self.db.update_dm_delivery_info(dm_id, 1, max_attempts, path_desc)
|
self.db.update_dm_delivery_info(dm_id, 1, max_attempts, path_desc)
|
||||||
|
if self.socketio:
|
||||||
|
self.socketio.emit('dm_delivered_info', {
|
||||||
|
'dm_id': dm_id, 'attempt': 1,
|
||||||
|
'max_attempts': max_attempts, 'path': path_desc,
|
||||||
|
}, namespace='/chat')
|
||||||
return
|
return
|
||||||
logger.debug(f"DM retry: initial ACK not received (timeout)")
|
logger.debug(f"DM retry: initial ACK not received (timeout)")
|
||||||
|
|
||||||
@@ -1418,7 +1439,7 @@ class DeviceManager:
|
|||||||
logger.info("DM retry: direct exhausted, resetting to FLOOD")
|
logger.info("DM retry: direct exhausted, resetting to FLOOD")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
path_desc = "FLOOD"
|
path_desc = ''
|
||||||
for _ in range(cfg['direct_flood_retries']):
|
for _ in range(cfg['direct_flood_retries']):
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if await _retry(attempt, float(cfg['flood_interval'])):
|
if await _retry(attempt, float(cfg['flood_interval'])):
|
||||||
@@ -1443,7 +1464,7 @@ class DeviceManager:
|
|||||||
try:
|
try:
|
||||||
await self._change_path_async(contact, path_info['path_hex'], path_info['hash_size'])
|
await self._change_path_async(contact, path_info['path_hex'], path_info['hash_size'])
|
||||||
label = path_info.get('label', '')
|
label = path_info.get('label', '')
|
||||||
path_desc = f"{label} ({path_info['path_hex']})" if label else path_info['path_hex']
|
path_desc = path_info['path_hex']
|
||||||
logger.info(f"DM retry: switched to path '{label}' ({path_info['path_hex']})")
|
logger.info(f"DM retry: switched to path '{label}' ({path_info['path_hex']})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"DM retry: failed to switch path: {e}")
|
logger.warning(f"DM retry: failed to switch path: {e}")
|
||||||
@@ -1482,7 +1503,7 @@ class DeviceManager:
|
|||||||
try:
|
try:
|
||||||
await self._change_path_async(contact, path_info['path_hex'], path_info['hash_size'])
|
await self._change_path_async(contact, path_info['path_hex'], path_info['hash_size'])
|
||||||
label = path_info.get('label', '')
|
label = path_info.get('label', '')
|
||||||
path_desc = f"{label} ({path_info['path_hex']})" if label else path_info['path_hex']
|
path_desc = path_info['path_hex']
|
||||||
logger.info(f"DM retry: switched to path '{label}' ({path_info['path_hex']})")
|
logger.info(f"DM retry: switched to path '{label}' ({path_info['path_hex']})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"DM retry: failed to switch path: {e}")
|
logger.warning(f"DM retry: failed to switch path: {e}")
|
||||||
@@ -1501,7 +1522,7 @@ class DeviceManager:
|
|||||||
logger.info("DM retry: all paths exhausted, falling back to FLOOD")
|
logger.info("DM retry: all paths exhausted, falling back to FLOOD")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
path_desc = "FLOOD"
|
path_desc = ''
|
||||||
for _ in range(cfg['flood_max_retries']):
|
for _ in range(cfg['flood_max_retries']):
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if await _retry(attempt, float(cfg['flood_interval'])):
|
if await _retry(attempt, float(cfg['flood_interval'])):
|
||||||
@@ -1512,7 +1533,7 @@ class DeviceManager:
|
|||||||
await self._restore_primary_path(contact, contact_pubkey)
|
await self._restore_primary_path(contact, contact_pubkey)
|
||||||
|
|
||||||
# ── Common epilogue: mark failed, grace period for late ACKs ──
|
# ── Common epilogue: mark failed, grace period for late ACKs ──
|
||||||
self.db.update_dm_delivery_info(dm_id, attempt + 1, max_attempts, path_desc)
|
self.db.update_dm_delivery_info(dm_id, attempt + 1, max_attempts, '')
|
||||||
self.db.update_dm_delivery_status(dm_id, 'failed')
|
self.db.update_dm_delivery_status(dm_id, 'failed')
|
||||||
self._emit_retry_failed(dm_id, initial_ack)
|
self._emit_retry_failed(dm_id, initial_ack)
|
||||||
logger.warning(f"DM retry exhausted ({attempt + 1} total attempts, scenario={scenario}) "
|
logger.warning(f"DM retry exhausted ({attempt + 1} total attempts, scenario={scenario}) "
|
||||||
|
|||||||
@@ -150,6 +150,31 @@ function connectChatSocket() {
|
|||||||
if (info) info.textContent = '';
|
if (info) info.textContent = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Real-time delivery info — show attempt count + route after successful delivery
|
||||||
|
chatSocket.on('dm_delivered_info', (data) => {
|
||||||
|
if (!data.dm_id) return;
|
||||||
|
// Find the message element containing this dm_id
|
||||||
|
const retryEl = document.querySelector(`.dm-retry-info[data-dm-id="${data.dm_id}"]`);
|
||||||
|
if (!retryEl) return;
|
||||||
|
retryEl.textContent = '';
|
||||||
|
const msgDiv = retryEl.closest('.dm-message');
|
||||||
|
if (!msgDiv) return;
|
||||||
|
// Build delivery meta text
|
||||||
|
const parts = [];
|
||||||
|
if (data.attempt && data.max_attempts) parts.push(`Attempt ${data.attempt}/${data.max_attempts}`);
|
||||||
|
if (data.path) parts.push(`Route: ${formatDmRoute(data.path)}`);
|
||||||
|
if (parts.length > 0) {
|
||||||
|
let metaEl = msgDiv.querySelector('.dm-delivery-meta');
|
||||||
|
if (!metaEl) {
|
||||||
|
metaEl = document.createElement('div');
|
||||||
|
metaEl.className = 'dm-delivery-meta';
|
||||||
|
const contentDiv = msgDiv.querySelector('div:nth-child(2)');
|
||||||
|
if (contentDiv) contentDiv.after(metaEl);
|
||||||
|
}
|
||||||
|
metaEl.textContent = parts.join(', ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Real-time device status
|
// Real-time device status
|
||||||
chatSocket.on('device_status', (data) => {
|
chatSocket.on('device_status', (data) => {
|
||||||
updateStatus(data.connected ? 'connected' : 'disconnected');
|
updateStatus(data.connected ? 'connected' : 'disconnected');
|
||||||
@@ -1129,7 +1154,7 @@ function displayMessages(messages) {
|
|||||||
if (msg.delivery_attempt && msg.delivery_max_attempts) {
|
if (msg.delivery_attempt && msg.delivery_max_attempts) {
|
||||||
title += ` (${msg.delivery_attempt}/${msg.delivery_max_attempts})`;
|
title += ` (${msg.delivery_attempt}/${msg.delivery_max_attempts})`;
|
||||||
}
|
}
|
||||||
if (msg.delivery_path) title += `, Path: ${msg.delivery_path}`;
|
if (msg.delivery_path) title += `, Route: ${formatDmRoute(msg.delivery_path)}`;
|
||||||
if (msg.delivery_snr !== null && msg.delivery_snr !== undefined) {
|
if (msg.delivery_snr !== null && msg.delivery_snr !== undefined) {
|
||||||
title += `, SNR: ${msg.delivery_snr.toFixed(1)} dB`;
|
title += `, SNR: ${msg.delivery_snr.toFixed(1)} dB`;
|
||||||
}
|
}
|
||||||
@@ -1157,15 +1182,18 @@ function displayMessages(messages) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delivery info for delivered/failed messages (attempt count + path)
|
// Delivery info for delivered/failed messages (attempt count + route)
|
||||||
let deliveryMeta = '';
|
let deliveryMeta = '';
|
||||||
if (msg.is_own && (msg.status === 'delivered' || msg.status === 'failed')
|
if (msg.is_own && (msg.status === 'delivered' || msg.status === 'failed')
|
||||||
&& (msg.delivery_attempt || msg.delivery_path)) {
|
&& msg.delivery_attempt) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (msg.delivery_attempt && msg.delivery_max_attempts) {
|
if (msg.delivery_attempt && msg.delivery_max_attempts) {
|
||||||
parts.push(`Attempt ${msg.delivery_attempt}/${msg.delivery_max_attempts}`);
|
parts.push(`Attempt ${msg.delivery_attempt}/${msg.delivery_max_attempts}`);
|
||||||
}
|
}
|
||||||
if (msg.delivery_path) parts.push(`Path: ${msg.delivery_path}`);
|
// Show route only for delivered messages (not failed)
|
||||||
|
if (msg.status === 'delivered' && msg.delivery_path) {
|
||||||
|
parts.push(`Route: ${formatDmRoute(msg.delivery_path)}`);
|
||||||
|
}
|
||||||
deliveryMeta = `<div class="dm-delivery-meta">${parts.join(', ')}</div>`;
|
deliveryMeta = `<div class="dm-delivery-meta">${parts.join(', ')}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1335,6 +1363,20 @@ function resendMessage(content) {
|
|||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a hex path as route string (e.g. "5e34e761" → "5e→34→e7→61")
|
||||||
|
* Truncates if more than 4 segments.
|
||||||
|
*/
|
||||||
|
function formatDmRoute(hexPath) {
|
||||||
|
if (!hexPath) return '';
|
||||||
|
const segments = hexPath.match(/.{1,2}/g) || [];
|
||||||
|
if (segments.length === 0) return '';
|
||||||
|
if (segments.length > 4) {
|
||||||
|
return `${segments[0]}\u2192...\u2192${segments[segments.length - 1]}`;
|
||||||
|
}
|
||||||
|
return segments.join('\u2192');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show delivery info popup (mobile-friendly, same pattern as showPathPopup)
|
* Show delivery info popup (mobile-friendly, same pattern as showPathPopup)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user