mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-07 17:22:57 +02:00
fix(dm): move retry counter below message, show delivery info visually
Move the attempt counter (e.g. "Attempt 15/24") from next to the status icon to below the message text, left of the Resend button. Add visible delivery meta line for delivered/failed messages showing attempt count and path used. Store attempt info for failed messages too. Replace Polish abbreviations (ŚK, ŚD, ŚG) with English in all log messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-15
@@ -1275,8 +1275,8 @@ class DeviceManager:
|
||||
4-scenario matrix based on (has_path × has_configured_paths):
|
||||
- Scenario 1: No path, no configured paths → FLOOD only
|
||||
- Scenario 2: Has path, no configured paths → DIRECT + optional FLOOD
|
||||
- Scenario 3: No path, has configured paths → FLOOD first, then ŚD rotation
|
||||
- Scenario 4: Has path, has configured paths → DIRECT on ŚK, ŚD rotation, optional FLOOD
|
||||
- Scenario 3: No path, has configured paths → FLOOD first, then configured path rotation
|
||||
- Scenario 4: Has path, has configured paths → DIRECT on current path, configured path rotation, optional FLOOD
|
||||
|
||||
The no_auto_flood per-contact flag prevents automatic DIRECT→FLOOD reset
|
||||
in Scenarios 2 and 4. Ignored in Scenarios 1 and 3.
|
||||
@@ -1405,7 +1405,7 @@ class DeviceManager:
|
||||
# Scenario 2: Has path, no configured paths → DIRECT + optional FLOOD
|
||||
# ════════════════════════════════════════════════════════════
|
||||
elif has_path and not has_configured_paths:
|
||||
# Phase 1: Direct retries on current ŚK
|
||||
# Phase 1: Direct retries on current path
|
||||
for _ in range(cfg['direct_max_retries']):
|
||||
attempt += 1
|
||||
if await _retry(attempt, float(cfg['direct_interval'])):
|
||||
@@ -1425,7 +1425,7 @@ class DeviceManager:
|
||||
return
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# Scenario 3: No path, has configured paths → FLOOD first, then ŚD rotation
|
||||
# Scenario 3: No path, has configured paths → FLOOD first, then configured path rotation
|
||||
# ════════════════════════════════════════════════════════════
|
||||
elif not has_path and has_configured_paths:
|
||||
# Phase 1: FLOOD retries per NoPath settings (discover new path)
|
||||
@@ -1433,9 +1433,9 @@ class DeviceManager:
|
||||
for _ in range(cfg['flood_max_retries']):
|
||||
attempt += 1
|
||||
if await _retry(attempt, float(cfg['flood_interval'])):
|
||||
return # Firmware sets discovered path as ŚK
|
||||
return # Firmware sets discovered path automatically
|
||||
|
||||
# Phase 2: ŚD rotation (primary first, then others by sort_order)
|
||||
# Phase 2: Configured path rotation (primary first, then others by sort_order)
|
||||
logger.info("DM retry: FLOOD exhausted, rotating through configured paths")
|
||||
direct_interval = float(cfg['direct_interval'])
|
||||
|
||||
@@ -1455,28 +1455,28 @@ class DeviceManager:
|
||||
await self._restore_primary_path(contact, contact_pubkey)
|
||||
return
|
||||
|
||||
# Restore ŚG regardless of outcome
|
||||
# Restore primary path regardless of outcome
|
||||
await self._restore_primary_path(contact, contact_pubkey)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# Scenario 4: Has path + has configured paths → DIRECT on ŚK, ŚD rotation, optional FLOOD
|
||||
# Scenario 4: Has path + has configured paths → DIRECT on current path, configured path rotation, optional FLOOD
|
||||
# ════════════════════════════════════════════════════════════
|
||||
else: # has_path and has_configured_paths
|
||||
# Phase 1: Direct retries on current ŚK
|
||||
# Phase 1: Direct retries on current path
|
||||
for _ in range(cfg['direct_max_retries']):
|
||||
attempt += 1
|
||||
if await _retry(attempt, float(cfg['direct_interval'])):
|
||||
return # Delivered on ŚK, no path change needed
|
||||
return # Delivered on current path, no change needed
|
||||
|
||||
# Phase 2: ŚD rotation with dedup
|
||||
logger.info("DM retry: direct on ŚK exhausted, rotating through configured paths")
|
||||
# Phase 2: Configured path rotation with dedup
|
||||
logger.info("DM retry: direct retries exhausted, rotating through configured paths")
|
||||
direct_interval = float(cfg['direct_interval'])
|
||||
|
||||
for path_info in rotation_order:
|
||||
# Dedup: skip if this configured path matches original ŚK
|
||||
# Dedup: skip if this configured path matches original device path
|
||||
if self._paths_match(original_out_path, original_out_path_len, path_info):
|
||||
logger.debug(f"DM retry: skipping path '{path_info.get('label', '')}' "
|
||||
f"({path_info['path_hex']}) — matches current ŚK")
|
||||
f"({path_info['path_hex']}) — matches current device path")
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -1508,10 +1508,11 @@ class DeviceManager:
|
||||
await self._restore_primary_path(contact, contact_pubkey)
|
||||
return
|
||||
|
||||
# Restore ŚG regardless of outcome
|
||||
# Restore primary path regardless of outcome
|
||||
await self._restore_primary_path(contact, contact_pubkey)
|
||||
|
||||
# ── 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_status(dm_id, 'failed')
|
||||
self._emit_retry_failed(dm_id, initial_ack)
|
||||
logger.warning(f"DM retry exhausted ({attempt + 1} total attempts, scenario={scenario}) "
|
||||
|
||||
@@ -739,9 +739,15 @@ main {
|
||||
}
|
||||
|
||||
.dm-retry-info {
|
||||
font-size: 0.6rem;
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-meta);
|
||||
margin-left: 0.15rem;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.dm-delivery-meta {
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-meta);
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
.dm-delivery-popup {
|
||||
@@ -763,6 +769,7 @@ main {
|
||||
.dm-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
|
||||
+35
-21
@@ -112,14 +112,14 @@ function connectChatSocket() {
|
||||
if (data.snr != null) tooltip.push(`SNR: ${data.snr}`);
|
||||
if (data.route_type) tooltip.push(`Route: ${data.route_type}`);
|
||||
statusEl.title = tooltip.length > 0 ? tooltip.join(', ') : 'Delivered';
|
||||
// Remove retry counter if present
|
||||
// Unwrap status icon from wrapper span
|
||||
const wrapper = statusEl.closest('[data-dm-id]');
|
||||
if (wrapper) {
|
||||
const info = wrapper.querySelector('.dm-retry-info');
|
||||
if (info) info.remove();
|
||||
// Unwrap: replace wrapper span with just the icon
|
||||
wrapper.replaceWith(statusEl);
|
||||
}
|
||||
// Clear retry counter in actions area
|
||||
const retryInfo = el.querySelector('.dm-retry-info');
|
||||
if (retryInfo) retryInfo.textContent = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -127,27 +127,27 @@ function connectChatSocket() {
|
||||
// Real-time DM retry progress
|
||||
chatSocket.on('dm_retry_status', (data) => {
|
||||
if (!data.dm_id) return;
|
||||
const wrapper = document.querySelector(`[data-dm-id="${data.dm_id}"]`);
|
||||
if (!wrapper) return;
|
||||
const info = wrapper.querySelector('.dm-retry-info');
|
||||
if (info) info.textContent = `${data.attempt}/${data.max_attempts}`;
|
||||
const info = document.querySelector(`.dm-retry-info[data-dm-id="${data.dm_id}"]`);
|
||||
if (info) info.textContent = `Attempt ${data.attempt}/${data.max_attempts}`;
|
||||
});
|
||||
|
||||
// DM retry exhausted — mark as failed
|
||||
chatSocket.on('dm_retry_failed', (data) => {
|
||||
if (!data.dm_id) return;
|
||||
const wrapper = document.querySelector(`[data-dm-id="${data.dm_id}"]`);
|
||||
if (!wrapper) return;
|
||||
const icon = wrapper.querySelector('.dm-status');
|
||||
if (icon) {
|
||||
icon.className = 'bi bi-x-circle dm-status timeout';
|
||||
icon.title = 'Delivery failed — all retries exhausted';
|
||||
// Update status icon
|
||||
const wrapper = document.querySelector(`.dm-status-unknown[data-dm-id="${data.dm_id}"]`);
|
||||
if (wrapper) {
|
||||
const icon = wrapper.querySelector('.dm-status');
|
||||
if (icon) {
|
||||
icon.className = 'bi bi-x-circle dm-status timeout';
|
||||
icon.title = 'Delivery failed — all retries exhausted';
|
||||
}
|
||||
wrapper.removeAttribute('onclick');
|
||||
wrapper.classList.remove('dm-status-unknown');
|
||||
}
|
||||
const info = wrapper.querySelector('.dm-retry-info');
|
||||
if (info) info.remove();
|
||||
// Remove onclick
|
||||
wrapper.removeAttribute('onclick');
|
||||
wrapper.classList.remove('dm-status-unknown');
|
||||
// Clear retry counter
|
||||
const info = document.querySelector(`.dm-retry-info[data-dm-id="${data.dm_id}"]`);
|
||||
if (info) info.textContent = '';
|
||||
});
|
||||
|
||||
// Real-time device status
|
||||
@@ -1141,7 +1141,7 @@ function displayMessages(messages) {
|
||||
statusIcon = `<i class="bi bi-clock dm-status pending"${ackAttr} title="Sending..."></i>`;
|
||||
} else {
|
||||
// No ACK received — show clickable "?" with retry counter
|
||||
statusIcon = `<span class="dm-status-unknown"${dmIdAttr} onclick="showDeliveryInfo(this)"><i class="bi bi-question-circle dm-status unknown"${ackAttr}></i><span class="dm-retry-info"></span></span>`;
|
||||
statusIcon = `<span class="dm-status-unknown"${dmIdAttr} onclick="showDeliveryInfo(this)"><i class="bi bi-question-circle dm-status unknown"${ackAttr}></i></span>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1157,9 +1157,22 @@ function displayMessages(messages) {
|
||||
}
|
||||
}
|
||||
|
||||
// Resend button for own messages
|
||||
// Delivery info for delivered/failed messages (attempt count + path)
|
||||
let deliveryMeta = '';
|
||||
if (msg.is_own && (msg.status === 'delivered' || msg.status === 'failed')
|
||||
&& (msg.delivery_attempt || msg.delivery_path)) {
|
||||
const parts = [];
|
||||
if (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}`);
|
||||
deliveryMeta = `<div class="dm-delivery-meta">${parts.join(', ')}</div>`;
|
||||
}
|
||||
|
||||
// Resend button for own messages (with retry counter placeholder)
|
||||
const resendBtn = msg.is_own ? `
|
||||
<div class="dm-actions">
|
||||
<span class="dm-retry-info" data-dm-id="${msg.id || ''}"></span>
|
||||
<button class="btn btn-outline-secondary btn-sm dm-action-btn" onclick='resendMessage(${JSON.stringify(msg.content)})' title="Resend">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
@@ -1172,6 +1185,7 @@ function displayMessages(messages) {
|
||||
${statusIcon}
|
||||
</div>
|
||||
<div>${processMessageContent(msg.content)}</div>
|
||||
${deliveryMeta}
|
||||
${meta}
|
||||
${resendBtn}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user