mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-10 10:43:01 +02:00
Add room activity to stats view
This commit is contained in:
+1
-1
@@ -330,7 +330,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### meshcore (2.3.1) — MIT
|
### meshcore (2.3.2) — MIT
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Full license text</summary>
|
<summary>Full license text</summary>
|
||||||
|
|||||||
@@ -833,4 +833,5 @@ class StatisticsResponse(BaseModel):
|
|||||||
total_outgoing: int
|
total_outgoing: int
|
||||||
contacts_heard: ContactActivityCounts
|
contacts_heard: ContactActivityCounts
|
||||||
repeaters_heard: ContactActivityCounts
|
repeaters_heard: ContactActivityCounts
|
||||||
|
known_channels_active: ContactActivityCounts
|
||||||
path_hash_width_24h: PathHashWidthStats
|
path_hash_width_24h: PathHashWidthStats
|
||||||
|
|||||||
@@ -270,6 +270,30 @@ class StatisticsRepository:
|
|||||||
"last_week": row["last_week"] or 0,
|
"last_week": row["last_week"] or 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _known_channels_active() -> dict[str, int]:
|
||||||
|
"""Count distinct known channel keys with channel traffic in each time window."""
|
||||||
|
now = int(time.time())
|
||||||
|
cursor = await db.conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT CASE WHEN m.received_at >= ? THEN m.conversation_key END) AS last_hour,
|
||||||
|
COUNT(DISTINCT CASE WHEN m.received_at >= ? THEN m.conversation_key END) AS last_24_hours,
|
||||||
|
COUNT(DISTINCT CASE WHEN m.received_at >= ? THEN m.conversation_key END) AS last_week
|
||||||
|
FROM messages m
|
||||||
|
INNER JOIN channels c ON UPPER(m.conversation_key) = UPPER(c.key)
|
||||||
|
WHERE m.type = 'CHAN'
|
||||||
|
""",
|
||||||
|
(now - SECONDS_1H, now - SECONDS_24H, now - SECONDS_7D),
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
assert row is not None
|
||||||
|
return {
|
||||||
|
"last_hour": row["last_hour"] or 0,
|
||||||
|
"last_24_hours": row["last_24_hours"] or 0,
|
||||||
|
"last_week": row["last_week"] or 0,
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _path_hash_width_24h() -> dict[str, int | float]:
|
async def _path_hash_width_24h() -> dict[str, int | float]:
|
||||||
"""Count parsed raw packets from the last 24h by hop hash width."""
|
"""Count parsed raw packets from the last 24h by hop hash width."""
|
||||||
@@ -396,6 +420,7 @@ class StatisticsRepository:
|
|||||||
# Activity windows
|
# Activity windows
|
||||||
contacts_heard = await StatisticsRepository._activity_counts(contact_type=2, exclude=True)
|
contacts_heard = await StatisticsRepository._activity_counts(contact_type=2, exclude=True)
|
||||||
repeaters_heard = await StatisticsRepository._activity_counts(contact_type=2)
|
repeaters_heard = await StatisticsRepository._activity_counts(contact_type=2)
|
||||||
|
known_channels_active = await StatisticsRepository._known_channels_active()
|
||||||
path_hash_width_24h = await StatisticsRepository._path_hash_width_24h()
|
path_hash_width_24h = await StatisticsRepository._path_hash_width_24h()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -411,5 +436,6 @@ class StatisticsRepository:
|
|||||||
"total_outgoing": total_outgoing,
|
"total_outgoing": total_outgoing,
|
||||||
"contacts_heard": contacts_heard,
|
"contacts_heard": contacts_heard,
|
||||||
"repeaters_heard": repeaters_heard,
|
"repeaters_heard": repeaters_heard,
|
||||||
|
"known_channels_active": known_channels_active,
|
||||||
"path_hash_width_24h": path_hash_width_24h,
|
"path_hash_width_24h": path_hash_width_24h,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,12 @@ export function SettingsStatisticsSection({ className }: { className?: string })
|
|||||||
<td className="text-right py-1">{stats.repeaters_heard.last_24_hours}</td>
|
<td className="text-right py-1">{stats.repeaters_heard.last_24_hours}</td>
|
||||||
<td className="text-right py-1">{stats.repeaters_heard.last_week}</td>
|
<td className="text-right py-1">{stats.repeaters_heard.last_week}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="py-1">Known-channels active</td>
|
||||||
|
<td className="text-right py-1">{stats.known_channels_active.last_hour}</td>
|
||||||
|
<td className="text-right py-1">{stats.known_channels_active.last_24_hours}</td>
|
||||||
|
<td className="text-right py-1">{stats.known_channels_active.last_week}</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -584,6 +584,7 @@ describe('SettingsModal', () => {
|
|||||||
total_outgoing: 30,
|
total_outgoing: 30,
|
||||||
contacts_heard: { last_hour: 2, last_24_hours: 7, last_week: 10 },
|
contacts_heard: { last_hour: 2, last_24_hours: 7, last_week: 10 },
|
||||||
repeaters_heard: { last_hour: 1, last_24_hours: 3, last_week: 3 },
|
repeaters_heard: { last_hour: 1, last_24_hours: 3, last_week: 3 },
|
||||||
|
known_channels_active: { last_hour: 1, last_24_hours: 4, last_week: 6 },
|
||||||
path_hash_width_24h: {
|
path_hash_width_24h: {
|
||||||
total_packets: 120,
|
total_packets: 120,
|
||||||
single_byte: 60,
|
single_byte: 60,
|
||||||
@@ -630,6 +631,7 @@ describe('SettingsModal', () => {
|
|||||||
expect(screen.getByText('24 (20.0%)')).toBeInTheDocument();
|
expect(screen.getByText('24 (20.0%)')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Contacts heard')).toBeInTheDocument();
|
expect(screen.getByText('Contacts heard')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Repeaters heard')).toBeInTheDocument();
|
expect(screen.getByText('Repeaters heard')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Known-channels active')).toBeInTheDocument();
|
||||||
|
|
||||||
// Busiest channels
|
// Busiest channels
|
||||||
expect(screen.getByText('general')).toBeInTheDocument();
|
expect(screen.getByText('general')).toBeInTheDocument();
|
||||||
@@ -650,6 +652,7 @@ describe('SettingsModal', () => {
|
|||||||
total_outgoing: 30,
|
total_outgoing: 30,
|
||||||
contacts_heard: { last_hour: 2, last_24_hours: 7, last_week: 10 },
|
contacts_heard: { last_hour: 2, last_24_hours: 7, last_week: 10 },
|
||||||
repeaters_heard: { last_hour: 1, last_24_hours: 3, last_week: 3 },
|
repeaters_heard: { last_hour: 1, last_24_hours: 3, last_week: 3 },
|
||||||
|
known_channels_active: { last_hour: 1, last_24_hours: 4, last_week: 6 },
|
||||||
path_hash_width_24h: {
|
path_hash_width_24h: {
|
||||||
total_packets: 120,
|
total_packets: 120,
|
||||||
single_byte: 60,
|
single_byte: 60,
|
||||||
|
|||||||
@@ -514,6 +514,7 @@ export interface StatisticsResponse {
|
|||||||
total_outgoing: number;
|
total_outgoing: number;
|
||||||
contacts_heard: ContactActivityCounts;
|
contacts_heard: ContactActivityCounts;
|
||||||
repeaters_heard: ContactActivityCounts;
|
repeaters_heard: ContactActivityCounts;
|
||||||
|
known_channels_active: ContactActivityCounts;
|
||||||
path_hash_width_24h: {
|
path_hash_width_24h: {
|
||||||
total_packets: number;
|
total_packets: number;
|
||||||
single_byte: number;
|
single_byte: number;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ class TestStatisticsEmpty:
|
|||||||
assert result["repeaters_heard"]["last_hour"] == 0
|
assert result["repeaters_heard"]["last_hour"] == 0
|
||||||
assert result["repeaters_heard"]["last_24_hours"] == 0
|
assert result["repeaters_heard"]["last_24_hours"] == 0
|
||||||
assert result["repeaters_heard"]["last_week"] == 0
|
assert result["repeaters_heard"]["last_week"] == 0
|
||||||
|
assert result["known_channels_active"]["last_hour"] == 0
|
||||||
|
assert result["known_channels_active"]["last_24_hours"] == 0
|
||||||
|
assert result["known_channels_active"]["last_week"] == 0
|
||||||
assert result["path_hash_width_24h"] == {
|
assert result["path_hash_width_24h"] == {
|
||||||
"total_packets": 0,
|
"total_packets": 0,
|
||||||
"single_byte": 0,
|
"single_byte": 0,
|
||||||
@@ -256,6 +259,51 @@ class TestActivityWindows:
|
|||||||
assert result["repeaters_heard"]["last_24_hours"] == 1
|
assert result["repeaters_heard"]["last_24_hours"] == 1
|
||||||
assert result["repeaters_heard"]["last_week"] == 1
|
assert result["repeaters_heard"]["last_week"] == 1
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_known_channels_active_windows(self, test_db):
|
||||||
|
"""Known channels are counted by distinct active keys in each time window."""
|
||||||
|
now = int(time.time())
|
||||||
|
conn = test_db.conn
|
||||||
|
|
||||||
|
known_1h = "AA" * 16
|
||||||
|
known_24h = "BB" * 16
|
||||||
|
known_7d = "CC" * 16
|
||||||
|
unknown_key = "DD" * 16
|
||||||
|
|
||||||
|
await conn.execute("INSERT INTO channels (key, name) VALUES (?, ?)", (known_1h, "chan-1h"))
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO channels (key, name) VALUES (?, ?)", (known_24h, "chan-24h")
|
||||||
|
)
|
||||||
|
await conn.execute("INSERT INTO channels (key, name) VALUES (?, ?)", (known_7d, "chan-7d"))
|
||||||
|
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO messages (type, conversation_key, text, received_at) VALUES (?, ?, ?, ?)",
|
||||||
|
("CHAN", known_1h, "recent-1", now - 1200),
|
||||||
|
)
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO messages (type, conversation_key, text, received_at) VALUES (?, ?, ?, ?)",
|
||||||
|
("CHAN", known_1h, "recent-2", now - 600),
|
||||||
|
)
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO messages (type, conversation_key, text, received_at) VALUES (?, ?, ?, ?)",
|
||||||
|
("CHAN", known_24h, "day-old", now - 43200),
|
||||||
|
)
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO messages (type, conversation_key, text, received_at) VALUES (?, ?, ?, ?)",
|
||||||
|
("CHAN", known_7d, "week-old", now - 259200),
|
||||||
|
)
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO messages (type, conversation_key, text, received_at) VALUES (?, ?, ?, ?)",
|
||||||
|
("CHAN", unknown_key, "unknown", now - 600),
|
||||||
|
)
|
||||||
|
await conn.commit()
|
||||||
|
|
||||||
|
result = await StatisticsRepository.get_all()
|
||||||
|
|
||||||
|
assert result["known_channels_active"]["last_hour"] == 1
|
||||||
|
assert result["known_channels_active"]["last_24_hours"] == 2
|
||||||
|
assert result["known_channels_active"]["last_week"] == 3
|
||||||
|
|
||||||
|
|
||||||
class TestPathHashWidthStats:
|
class TestPathHashWidthStats:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
Reference in New Issue
Block a user