mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-30 21:43:14 +02:00
Plumb through total neighbor count from radio and expose that in the UI. Closes #310.
This commit is contained in:
@@ -672,6 +672,14 @@ class RepeaterNeighborsResponse(BaseModel):
|
||||
neighbors: list[NeighborInfo] = Field(
|
||||
default_factory=list, description="List of neighbors seen by repeater"
|
||||
)
|
||||
reported_count: int | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Total neighbor count reported by the repeater firmware, independent of "
|
||||
"how many entries were actually returned. May exceed len(neighbors) when a "
|
||||
"multi-chunk fetch is incomplete (dropped follow-up query / duty-cycle throttle)."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class RepeaterAclResponse(BaseModel):
|
||||
|
||||
@@ -265,7 +265,8 @@ async def repeater_neighbors(public_key: str) -> RepeaterNeighborsResponse:
|
||||
)
|
||||
)
|
||||
|
||||
return RepeaterNeighborsResponse(neighbors=neighbors)
|
||||
reported_count = neighbors_data.get("neighbours_count") if neighbors_data else None
|
||||
return RepeaterNeighborsResponse(neighbors=neighbors, reported_count=reported_count)
|
||||
|
||||
|
||||
@router.post("/{public_key}/repeater/acl", response_model=RepeaterAclResponse)
|
||||
|
||||
@@ -203,7 +203,13 @@ export function NeighborsPane({
|
||||
|
||||
return (
|
||||
<RepeaterPane
|
||||
title="Neighbors"
|
||||
title={
|
||||
!data
|
||||
? 'Neighbors'
|
||||
: data.reported_count != null && data.reported_count !== data.neighbors.length
|
||||
? `Neighbors (${data.neighbors.length} of ${data.reported_count})`
|
||||
: `Neighbors (${data.reported_count ?? data.neighbors.length})`
|
||||
}
|
||||
headerNote={headerNote}
|
||||
state={state}
|
||||
onRefresh={onRefresh}
|
||||
|
||||
@@ -467,6 +467,10 @@ export interface RepeaterStatusResponse {
|
||||
|
||||
export interface RepeaterNeighborsResponse {
|
||||
neighbors: NeighborInfo[];
|
||||
// Total neighbor count reported by the repeater firmware, independent of how many
|
||||
// entries were actually returned. Exceeds neighbors.length when a multi-chunk fetch
|
||||
// is incomplete. Null on older firmware / failed fetches.
|
||||
reported_count?: number | null;
|
||||
}
|
||||
|
||||
export interface RepeaterAclResponse {
|
||||
|
||||
@@ -873,6 +873,35 @@ class TestRepeaterNeighbors:
|
||||
assert response.neighbors[0].snr == 9.0
|
||||
assert response.neighbors[1].name is None
|
||||
assert response.neighbors[1].last_heard_seconds == 120
|
||||
# No firmware-reported total in this payload → reported_count stays None.
|
||||
assert response.reported_count is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reported_count_captured_on_partial_fetch(self, test_db):
|
||||
"""Firmware neighbours_count is surfaced even when fewer entries return."""
|
||||
mc = _mock_mc()
|
||||
await _insert_contact(KEY_A, name="Repeater", contact_type=2)
|
||||
# Repeater claims 30 neighbours but only 2 came back this fetch (dropped
|
||||
# multi-chunk follow-up); reported_count must reflect the true total.
|
||||
mc.commands.fetch_all_neighbours = AsyncMock(
|
||||
return_value={
|
||||
"neighbours_count": 30,
|
||||
"results_count": 2,
|
||||
"neighbours": [
|
||||
{"pubkey": "aaaaaaaaaaaa", "snr": 9.0, "secs_ago": 5},
|
||||
{"pubkey": "cccccccccccc", "snr": 3.0, "secs_ago": 120},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
with (
|
||||
patch("app.routers.repeaters.radio_manager.require_connected", return_value=mc),
|
||||
patch.object(radio_manager, "_meshcore", mc),
|
||||
):
|
||||
response = await repeater_neighbors(KEY_A)
|
||||
|
||||
assert len(response.neighbors) == 2
|
||||
assert response.reported_count == 30
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_neighbors(self, test_db):
|
||||
|
||||
Reference in New Issue
Block a user