mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-01 02:53:00 +02:00
Fix some frontend display/quality/doc issues
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -3,7 +3,7 @@
|
||||
* Feature: Add Arch AUR package
|
||||
* Feature: 72hr packet density view in statistics
|
||||
* Feature: Add warnings for event loop selection for MQTT on Windows startup
|
||||
* Bufix: Bump Apprise to 1.9.9 to fix Matrix bug
|
||||
* Bugfix: Bump Apprise to 1.9.9 to fix Matrix bug
|
||||
* Misc: More memory-conscious on recent contact fetch
|
||||
* Misc: Fix statistics pane e2e test
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
* Bugfix: Fix Apprise duplicate names
|
||||
* Bugfix: Be better about identity resolution in the stats pane
|
||||
* Misc: Docs, test, and performance enhancements
|
||||
* Misc: Don't prompt "Are you sure" when leaving an unedited interation
|
||||
* Misc: Don't prompt "Are you sure" when leaving an unedited integration
|
||||
* Misc: Log node time on startup
|
||||
* Misc: Improve community MQTT error bubble-up
|
||||
* Misc: Unread DMs always have a red unread counter
|
||||
@@ -172,7 +172,7 @@
|
||||
## [3.3.0] - 2026-03-13
|
||||
|
||||
* Feature: Use dashed lines to show collapsed ambiguous router results
|
||||
* Feature: Jump to unred
|
||||
* Feature: Jump to unread
|
||||
* Feature: Local channel management to prevent need to reload channel every time
|
||||
* Feature: Debug endpoint
|
||||
* Feature: Force-singleton channel management
|
||||
@@ -235,7 +235,7 @@
|
||||
* Feature: Massive codebase refactor and overhaul
|
||||
* Bugfix: Fix packet parsing for trace packets
|
||||
* Bugfix: Refetch channels on reconnect
|
||||
* Bugfix: Load All on repeater pane on mobile doesn't etend into lower text
|
||||
* Bugfix: Load All on repeater pane on mobile doesn't extend into lower text
|
||||
* Bugfix: Timestamps in logs
|
||||
* Bugfix: Correct wrong clock sync command
|
||||
* Misc: Improve bot error bubble up
|
||||
@@ -252,10 +252,6 @@
|
||||
|
||||
* Bugfix: Don't obscure new integration dropdown on session boundary
|
||||
|
||||
## [2.7.8] - 2026-03-08
|
||||
|
||||
|
||||
|
||||
## [2.7.8] - 2026-03-08
|
||||
|
||||
* Bugfix: Improve frontend asset resolution and fixup the build/push script
|
||||
|
||||
@@ -23,7 +23,7 @@ For advanced setup and troubleshooting see [README_ADVANCED.md](README_ADVANCED.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- Python 3.11+
|
||||
- Node.js LTS or current (20, 22, 24, 25) if you're not using a prebuilt release
|
||||
- [UV](https://astral.sh/uv) package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||
- MeshCore radio connected via USB serial, TCP, or BLE
|
||||
@@ -135,6 +135,8 @@ sudo docker compose pull
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
> If you switched to a local build (`build: .` instead of `image:`), use `sudo docker compose up -d --build` instead — `pull` only fetches remote images.
|
||||
|
||||
The example file and setup script default to the published Docker Hub image. To build locally from your checkout instead, replace:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, NotRequired
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
from typing_extensions import NotRequired, TypedDict
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from app.models import Channel, Contact, Message, MessagePath, RawPacketBroadcast
|
||||
from app.routers.health import HealthResponse
|
||||
|
||||
@@ -164,7 +164,7 @@ class BotModule(FanoutModule):
|
||||
),
|
||||
timeout=BOT_EXECUTION_TIMEOUT,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning("Bot '%s' execution timed out", self.name)
|
||||
return
|
||||
except Exception:
|
||||
|
||||
@@ -538,7 +538,7 @@ class CommunityMqttPublisher(BaseMqttPublisher):
|
||||
self._version_event.clear()
|
||||
try:
|
||||
await asyncio.wait_for(self._version_event.wait(), timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
pass
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -225,7 +225,7 @@ class FanoutManager:
|
||||
handler = getattr(module, handler_name)
|
||||
await asyncio.wait_for(handler(data), timeout=_DISPATCH_TIMEOUT_SECONDS)
|
||||
self._clear_module_error(config_id)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
timeout_error = f"{handler_name} timed out after {_DISPATCH_TIMEOUT_SECONDS:.1f}s"
|
||||
self._set_module_error(config_id, timeout_error)
|
||||
logger.error(
|
||||
|
||||
@@ -196,7 +196,7 @@ class BaseMqttPublisher(ABC):
|
||||
self._version_event.wait(),
|
||||
timeout=self._not_configured_timeout,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
@@ -231,7 +231,7 @@ class BaseMqttPublisher(ABC):
|
||||
self._version_event.clear()
|
||||
try:
|
||||
await asyncio.wait_for(self._version_event.wait(), timeout=60)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
elapsed = time.monotonic() - connect_time
|
||||
await self._on_periodic_wake(elapsed)
|
||||
if self._should_break_wait(elapsed):
|
||||
|
||||
@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
NO_EVENT_RECEIVED_GUIDANCE = (
|
||||
"Radio command channel is unresponsive (no_event_received). Ensure that your firmware is not "
|
||||
"incompatible, outdated, or wrong-mode (e.g. repeater, not client), and that"
|
||||
"incompatible, outdated, or wrong-mode (e.g. repeater, not client), and that "
|
||||
"serial/TCP/BLE connectivity is successful (try another app and see if that one works?). The app cannot proceed because it cannot "
|
||||
"issue commands to the radio."
|
||||
)
|
||||
|
||||
@@ -118,7 +118,7 @@ async def test_serial_device(port: str, baudrate: int, timeout: float = 3.0) ->
|
||||
return True
|
||||
|
||||
return False
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.debug("Device %s timed out", port)
|
||||
return False
|
||||
except Exception as e:
|
||||
|
||||
@@ -480,7 +480,7 @@ async def drain_pending_messages(mc: MeshCore) -> int:
|
||||
# Small delay between fetches
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning("Error draining messages: %s", e, exc_info=True)
|
||||
@@ -518,7 +518,7 @@ async def poll_for_messages(mc: MeshCore) -> int:
|
||||
# If we got a message, there might be more - drain them
|
||||
count += await drain_pending_messages(mc)
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning("Message poll exception: %s", e, exc_info=True)
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import platform
|
||||
import struct
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter
|
||||
@@ -390,7 +390,7 @@ async def debug_support_snapshot() -> DebugSnapshotResponse:
|
||||
is_reconnecting=is_reconnecting,
|
||||
)
|
||||
return DebugSnapshotResponse(
|
||||
captured_at=datetime.now(timezone.utc).isoformat(),
|
||||
captured_at=datetime.now(UTC).isoformat(),
|
||||
system=_build_system_info(),
|
||||
application=_build_application_info(),
|
||||
health=_build_debug_health_summary(health_data, radio_state=radio_state),
|
||||
|
||||
@@ -473,7 +473,7 @@ async def discover_mesh(request: RadioDiscoveryRequest) -> RadioDiscoveryRespons
|
||||
break
|
||||
try:
|
||||
event = await asyncio.wait_for(events.get(), timeout=remaining)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
break
|
||||
|
||||
merged = _merge_discovery_result(
|
||||
@@ -536,7 +536,7 @@ async def trace_path(request: RadioTraceRequest) -> RadioTraceResponse:
|
||||
timeout_seconds = _trace_timeout_seconds(send_result)
|
||||
try:
|
||||
event = await asyncio.wait_for(response_task, timeout=timeout_seconds)
|
||||
except asyncio.TimeoutError as exc:
|
||||
except TimeoutError as exc:
|
||||
raise HTTPException(status_code=504, detail="No trace response heard") from exc
|
||||
finally:
|
||||
if not response_task.done():
|
||||
|
||||
@@ -94,7 +94,7 @@ async def fetch_contact_cli_response(
|
||||
while _monotonic() < deadline:
|
||||
try:
|
||||
result = await mc.commands.get_msg(timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
except Exception as exc:
|
||||
logger.debug("get_msg() exception: %s", exc)
|
||||
@@ -196,7 +196,7 @@ async def prepare_authenticated_contact_connection(
|
||||
login_future,
|
||||
timeout=response_timeout,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
"No login response from %s %s within %.1fs",
|
||||
contact_label,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -193,7 +193,7 @@ async def run_post_connect_setup(radio_manager) -> None:
|
||||
logger.info(
|
||||
"Radio clock at connect: epoch=%d utc=%s",
|
||||
radio_time,
|
||||
datetime.fromtimestamp(radio_time, timezone.utc).strftime(
|
||||
datetime.fromtimestamp(radio_time, UTC).strftime(
|
||||
"%Y-%m-%d %H:%M:%S UTC"
|
||||
),
|
||||
)
|
||||
@@ -274,7 +274,7 @@ async def prepare_connected_radio(radio_manager, *, broadcast_on_success: bool =
|
||||
try:
|
||||
await radio_manager.post_connect_setup()
|
||||
break
|
||||
except asyncio.TimeoutError as exc:
|
||||
except TimeoutError as exc:
|
||||
if attempt < POST_CONNECT_SETUP_MAX_ATTEMPTS:
|
||||
logger.warning(
|
||||
"Post-connect setup timed out after %ds on attempt %d/%d; retrying once",
|
||||
|
||||
@@ -13,13 +13,12 @@ import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tomllib
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tomllib
|
||||
|
||||
RELEASE_BUILD_INFO_FILENAME = "build_info.json"
|
||||
PROJECT_NAME = "remoteterm-meshcore"
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class WebSocketManager:
|
||||
try:
|
||||
# Timeout prevents blocking on slow/unresponsive clients
|
||||
await asyncio.wait_for(connection.send_text(message), timeout=SEND_TIMEOUT_SECONDS)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.debug("Timeout sending to WebSocket client, marking disconnected")
|
||||
disconnected.append(connection)
|
||||
except Exception as e:
|
||||
|
||||
@@ -9,7 +9,8 @@ import type {
|
||||
RadioTraceResponse,
|
||||
} from '../types';
|
||||
import { CONTACT_TYPE_REPEATER } from '../types';
|
||||
import { calculateDistance, isValidLocation } from '../utils/pathUtils';
|
||||
import { calculateDistance, formatDistance, isValidLocation } from '../utils/pathUtils';
|
||||
import { useDistanceUnit } from '../contexts/DistanceUnitContext';
|
||||
import { getContactDisplayName } from '../utils/pubkey';
|
||||
import { handleKeyboardActivate } from '../utils/a11y';
|
||||
import { ContactAvatar } from './ContactAvatar';
|
||||
@@ -186,6 +187,7 @@ function TraceNodeRow({
|
||||
}
|
||||
|
||||
export function TracePane({ contacts, config, onRunTracePath }: TracePaneProps) {
|
||||
const distanceUnit = useDistanceUnit();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortMode, setSortMode] = useState<TraceSortMode>('alpha');
|
||||
const [draftHops, setDraftHops] = useState<TraceDraftHop[]>([]);
|
||||
@@ -536,7 +538,7 @@ export function TracePane({ contacts, config, onRunTracePath }: TracePaneProps)
|
||||
</div>
|
||||
{sortMode === 'distance' && distanceKm !== null ? (
|
||||
<div className="mt-1 text-[0.6875rem] text-muted-foreground">
|
||||
{distanceKm.toFixed(1)} km away
|
||||
{formatDistance(distanceKm, distanceUnit)} away
|
||||
</div>
|
||||
) : null}
|
||||
{selectedCount > 0 ? (
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#MESHCORE_TCP_PORT=5000
|
||||
|
||||
# BLE (also requires the optional `bluez` package)
|
||||
# NOTE: The systemd service sets ProtectHome=yes, which may block the D-Bus
|
||||
# session bus at /run/user/. If BLE fails to connect, try overriding with
|
||||
# ProtectHome=no in a systemd drop-in.
|
||||
#MESHCORE_BLE_ADDRESS=AA:BB:CC:DD:EE:FF
|
||||
#MESHCORE_BLE_PIN=123456
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ testpaths = ["tests"]
|
||||
addopts = "-n auto --dist worksteal"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py310"
|
||||
target-version = "py311"
|
||||
line-length = 100
|
||||
|
||||
[tool.ruff.lint]
|
||||
|
||||
@@ -1073,9 +1073,7 @@ class TestPostConnectSetupOrdering:
|
||||
|
||||
rm = RadioManager()
|
||||
rm._connection_info = "Serial: /dev/ttyUSB0"
|
||||
rm.post_connect_setup = AsyncMock(
|
||||
side_effect=[asyncio.TimeoutError(), asyncio.TimeoutError()]
|
||||
)
|
||||
rm.post_connect_setup = AsyncMock(side_effect=[TimeoutError(), TimeoutError()])
|
||||
|
||||
with (
|
||||
patch("app.websocket.broadcast_error") as mock_broadcast_error,
|
||||
@@ -1099,7 +1097,7 @@ class TestPostConnectSetupOrdering:
|
||||
|
||||
rm = RadioManager()
|
||||
rm._connection_info = "Serial: /dev/ttyUSB0"
|
||||
rm.post_connect_setup = AsyncMock(side_effect=[asyncio.TimeoutError(), None])
|
||||
rm.post_connect_setup = AsyncMock(side_effect=[TimeoutError(), None])
|
||||
|
||||
with (
|
||||
patch("app.websocket.broadcast_error") as mock_broadcast_error,
|
||||
|
||||
Reference in New Issue
Block a user