mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-03-28 17:42:56 +01:00
Merge pull request #15 from ipnet-mesh/feature/originator-address
Updates
This commit is contained in:
@@ -5,10 +5,11 @@ from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from meshcore_hub.api.auth import RequireRead
|
||||
from meshcore_hub.api.dependencies import DbSession
|
||||
from meshcore_hub.common.models import Advertisement
|
||||
from meshcore_hub.common.models import Advertisement, Node
|
||||
from meshcore_hub.common.schemas.messages import AdvertisementList, AdvertisementRead
|
||||
|
||||
router = APIRouter()
|
||||
@@ -19,18 +20,29 @@ async def list_advertisements(
|
||||
_: RequireRead,
|
||||
session: DbSession,
|
||||
public_key: Optional[str] = Query(None, description="Filter by public key"),
|
||||
receiver_public_key: Optional[str] = Query(
|
||||
None, description="Filter by receiver node public key"
|
||||
),
|
||||
since: Optional[datetime] = Query(None, description="Start timestamp"),
|
||||
until: Optional[datetime] = Query(None, description="End timestamp"),
|
||||
limit: int = Query(50, ge=1, le=100, description="Page size"),
|
||||
offset: int = Query(0, ge=0, description="Page offset"),
|
||||
) -> AdvertisementList:
|
||||
"""List advertisements with filtering and pagination."""
|
||||
# Build query
|
||||
query = select(Advertisement)
|
||||
# Alias for receiver node join
|
||||
ReceiverNode = aliased(Node)
|
||||
|
||||
# Build query with receiver node join
|
||||
query = select(
|
||||
Advertisement, ReceiverNode.public_key.label("receiver_pk")
|
||||
).outerjoin(ReceiverNode, Advertisement.receiver_node_id == ReceiverNode.id)
|
||||
|
||||
if public_key:
|
||||
query = query.where(Advertisement.public_key == public_key)
|
||||
|
||||
if receiver_public_key:
|
||||
query = query.where(ReceiverNode.public_key == receiver_public_key)
|
||||
|
||||
if since:
|
||||
query = query.where(Advertisement.received_at >= since)
|
||||
|
||||
@@ -45,10 +57,27 @@ async def list_advertisements(
|
||||
query = query.order_by(Advertisement.received_at.desc()).offset(offset).limit(limit)
|
||||
|
||||
# Execute
|
||||
advertisements = session.execute(query).scalars().all()
|
||||
results = session.execute(query).all()
|
||||
|
||||
# Build response with receiver_public_key
|
||||
items = []
|
||||
for adv, receiver_pk in results:
|
||||
data = {
|
||||
"id": adv.id,
|
||||
"receiver_node_id": adv.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"node_id": adv.node_id,
|
||||
"public_key": adv.public_key,
|
||||
"name": adv.name,
|
||||
"adv_type": adv.adv_type,
|
||||
"flags": adv.flags,
|
||||
"received_at": adv.received_at,
|
||||
"created_at": adv.created_at,
|
||||
}
|
||||
items.append(AdvertisementRead(**data))
|
||||
|
||||
return AdvertisementList(
|
||||
items=[AdvertisementRead.model_validate(a) for a in advertisements],
|
||||
items=items,
|
||||
total=total,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
@@ -62,10 +91,28 @@ async def get_advertisement(
|
||||
advertisement_id: str,
|
||||
) -> AdvertisementRead:
|
||||
"""Get a single advertisement by ID."""
|
||||
query = select(Advertisement).where(Advertisement.id == advertisement_id)
|
||||
advertisement = session.execute(query).scalar_one_or_none()
|
||||
ReceiverNode = aliased(Node)
|
||||
query = (
|
||||
select(Advertisement, ReceiverNode.public_key.label("receiver_pk"))
|
||||
.outerjoin(ReceiverNode, Advertisement.receiver_node_id == ReceiverNode.id)
|
||||
.where(Advertisement.id == advertisement_id)
|
||||
)
|
||||
result = session.execute(query).one_or_none()
|
||||
|
||||
if not advertisement:
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Advertisement not found")
|
||||
|
||||
return AdvertisementRead.model_validate(advertisement)
|
||||
adv, receiver_pk = result
|
||||
data = {
|
||||
"id": adv.id,
|
||||
"receiver_node_id": adv.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"node_id": adv.node_id,
|
||||
"public_key": adv.public_key,
|
||||
"name": adv.name,
|
||||
"adv_type": adv.adv_type,
|
||||
"flags": adv.flags,
|
||||
"received_at": adv.received_at,
|
||||
"created_at": adv.created_at,
|
||||
}
|
||||
return AdvertisementRead(**data)
|
||||
|
||||
@@ -74,10 +74,20 @@ async def get_stats(
|
||||
.all()
|
||||
)
|
||||
|
||||
# Get friendly_name tags for the advertised nodes
|
||||
# Get node names and friendly_name tags for the advertised nodes
|
||||
ad_public_keys = [ad.public_key for ad in recent_ads]
|
||||
node_names: dict[str, str] = {}
|
||||
friendly_names: dict[str, str] = {}
|
||||
if ad_public_keys:
|
||||
# Get node names from Node table
|
||||
node_name_query = select(Node.public_key, Node.name).where(
|
||||
Node.public_key.in_(ad_public_keys)
|
||||
)
|
||||
for public_key, name in session.execute(node_name_query).all():
|
||||
if name:
|
||||
node_names[public_key] = name
|
||||
|
||||
# Get friendly_name tags
|
||||
friendly_name_query = (
|
||||
select(Node.public_key, NodeTag.value)
|
||||
.join(NodeTag, Node.id == NodeTag.node_id)
|
||||
@@ -90,7 +100,7 @@ async def get_stats(
|
||||
recent_advertisements = [
|
||||
RecentAdvertisement(
|
||||
public_key=ad.public_key,
|
||||
name=ad.name,
|
||||
name=ad.name or node_names.get(ad.public_key),
|
||||
friendly_name=friendly_names.get(ad.public_key),
|
||||
adv_type=ad.adv_type,
|
||||
received_at=ad.received_at,
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from meshcore_hub.api.auth import RequireRead
|
||||
from meshcore_hub.api.dependencies import DbSession
|
||||
@@ -21,6 +22,9 @@ async def list_messages(
|
||||
message_type: Optional[str] = Query(None, description="Filter by message type"),
|
||||
pubkey_prefix: Optional[str] = Query(None, description="Filter by sender prefix"),
|
||||
channel_idx: Optional[int] = Query(None, description="Filter by channel"),
|
||||
receiver_public_key: Optional[str] = Query(
|
||||
None, description="Filter by receiver node public key"
|
||||
),
|
||||
since: Optional[datetime] = Query(None, description="Start timestamp"),
|
||||
until: Optional[datetime] = Query(None, description="End timestamp"),
|
||||
search: Optional[str] = Query(None, description="Search in message text"),
|
||||
@@ -28,8 +32,13 @@ async def list_messages(
|
||||
offset: int = Query(0, ge=0, description="Page offset"),
|
||||
) -> MessageList:
|
||||
"""List messages with filtering and pagination."""
|
||||
# Build query
|
||||
query = select(Message)
|
||||
# Alias for receiver node join
|
||||
ReceiverNode = aliased(Node)
|
||||
|
||||
# Build query with receiver node join
|
||||
query = select(Message, ReceiverNode.public_key.label("receiver_pk")).outerjoin(
|
||||
ReceiverNode, Message.receiver_node_id == ReceiverNode.id
|
||||
)
|
||||
|
||||
if message_type:
|
||||
query = query.where(Message.message_type == message_type)
|
||||
@@ -40,6 +49,9 @@ async def list_messages(
|
||||
if channel_idx is not None:
|
||||
query = query.where(Message.channel_idx == channel_idx)
|
||||
|
||||
if receiver_public_key:
|
||||
query = query.where(ReceiverNode.public_key == receiver_public_key)
|
||||
|
||||
if since:
|
||||
query = query.where(Message.received_at >= since)
|
||||
|
||||
@@ -57,14 +69,24 @@ async def list_messages(
|
||||
query = query.order_by(Message.received_at.desc()).offset(offset).limit(limit)
|
||||
|
||||
# Execute
|
||||
messages = session.execute(query).scalars().all()
|
||||
results = session.execute(query).all()
|
||||
|
||||
# Look up friendly_names for senders with pubkey_prefix
|
||||
pubkey_prefixes = [m.pubkey_prefix for m in messages if m.pubkey_prefix]
|
||||
# Look up sender names and friendly_names for senders with pubkey_prefix
|
||||
pubkey_prefixes = [r[0].pubkey_prefix for r in results if r[0].pubkey_prefix]
|
||||
sender_names: dict[str, str] = {}
|
||||
friendly_names: dict[str, str] = {}
|
||||
if pubkey_prefixes:
|
||||
# Find nodes whose public_key starts with any of these prefixes
|
||||
for prefix in set(pubkey_prefixes):
|
||||
# Get node name
|
||||
node_query = select(Node.public_key, Node.name).where(
|
||||
Node.public_key.startswith(prefix)
|
||||
)
|
||||
for public_key, name in session.execute(node_query).all():
|
||||
if name:
|
||||
sender_names[public_key[:12]] = name
|
||||
|
||||
# Get friendly_name tag
|
||||
friendly_name_query = (
|
||||
select(Node.public_key, NodeTag.value)
|
||||
.join(NodeTag, Node.id == NodeTag.node_id)
|
||||
@@ -72,17 +94,20 @@ async def list_messages(
|
||||
.where(NodeTag.key == "friendly_name")
|
||||
)
|
||||
for public_key, value in session.execute(friendly_name_query).all():
|
||||
# Map the prefix to the friendly_name
|
||||
friendly_names[public_key[:12]] = value
|
||||
|
||||
# Build response with friendly_names
|
||||
# Build response with sender info and receiver_public_key
|
||||
items = []
|
||||
for m in messages:
|
||||
for m, receiver_pk in results:
|
||||
msg_dict = {
|
||||
"id": m.id,
|
||||
"receiver_node_id": m.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"message_type": m.message_type,
|
||||
"pubkey_prefix": m.pubkey_prefix,
|
||||
"sender_name": (
|
||||
sender_names.get(m.pubkey_prefix) if m.pubkey_prefix else None
|
||||
),
|
||||
"sender_friendly_name": (
|
||||
friendly_names.get(m.pubkey_prefix) if m.pubkey_prefix else None
|
||||
),
|
||||
@@ -113,10 +138,32 @@ async def get_message(
|
||||
message_id: str,
|
||||
) -> MessageRead:
|
||||
"""Get a single message by ID."""
|
||||
query = select(Message).where(Message.id == message_id)
|
||||
message = session.execute(query).scalar_one_or_none()
|
||||
ReceiverNode = aliased(Node)
|
||||
query = (
|
||||
select(Message, ReceiverNode.public_key.label("receiver_pk"))
|
||||
.outerjoin(ReceiverNode, Message.receiver_node_id == ReceiverNode.id)
|
||||
.where(Message.id == message_id)
|
||||
)
|
||||
result = session.execute(query).one_or_none()
|
||||
|
||||
if not message:
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Message not found")
|
||||
|
||||
return MessageRead.model_validate(message)
|
||||
message, receiver_pk = result
|
||||
data = {
|
||||
"id": message.id,
|
||||
"receiver_node_id": message.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"message_type": message.message_type,
|
||||
"pubkey_prefix": message.pubkey_prefix,
|
||||
"channel_idx": message.channel_idx,
|
||||
"text": message.text,
|
||||
"path_len": message.path_len,
|
||||
"txt_type": message.txt_type,
|
||||
"signature": message.signature,
|
||||
"snr": message.snr,
|
||||
"sender_timestamp": message.sender_timestamp,
|
||||
"received_at": message.received_at,
|
||||
"created_at": message.created_at,
|
||||
}
|
||||
return MessageRead(**data)
|
||||
|
||||
@@ -5,10 +5,11 @@ from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from meshcore_hub.api.auth import RequireRead
|
||||
from meshcore_hub.api.dependencies import DbSession
|
||||
from meshcore_hub.common.models import Telemetry
|
||||
from meshcore_hub.common.models import Node, Telemetry
|
||||
from meshcore_hub.common.schemas.messages import TelemetryList, TelemetryRead
|
||||
|
||||
router = APIRouter()
|
||||
@@ -19,18 +20,29 @@ async def list_telemetry(
|
||||
_: RequireRead,
|
||||
session: DbSession,
|
||||
node_public_key: Optional[str] = Query(None, description="Filter by node"),
|
||||
receiver_public_key: Optional[str] = Query(
|
||||
None, description="Filter by receiver node public key"
|
||||
),
|
||||
since: Optional[datetime] = Query(None, description="Start timestamp"),
|
||||
until: Optional[datetime] = Query(None, description="End timestamp"),
|
||||
limit: int = Query(50, ge=1, le=100, description="Page size"),
|
||||
offset: int = Query(0, ge=0, description="Page offset"),
|
||||
) -> TelemetryList:
|
||||
"""List telemetry records with filtering and pagination."""
|
||||
# Build query
|
||||
query = select(Telemetry)
|
||||
# Alias for receiver node join
|
||||
ReceiverNode = aliased(Node)
|
||||
|
||||
# Build query with receiver node join
|
||||
query = select(Telemetry, ReceiverNode.public_key.label("receiver_pk")).outerjoin(
|
||||
ReceiverNode, Telemetry.receiver_node_id == ReceiverNode.id
|
||||
)
|
||||
|
||||
if node_public_key:
|
||||
query = query.where(Telemetry.node_public_key == node_public_key)
|
||||
|
||||
if receiver_public_key:
|
||||
query = query.where(ReceiverNode.public_key == receiver_public_key)
|
||||
|
||||
if since:
|
||||
query = query.where(Telemetry.received_at >= since)
|
||||
|
||||
@@ -45,10 +57,25 @@ async def list_telemetry(
|
||||
query = query.order_by(Telemetry.received_at.desc()).offset(offset).limit(limit)
|
||||
|
||||
# Execute
|
||||
records = session.execute(query).scalars().all()
|
||||
results = session.execute(query).all()
|
||||
|
||||
# Build response with receiver_public_key
|
||||
items = []
|
||||
for tel, receiver_pk in results:
|
||||
data = {
|
||||
"id": tel.id,
|
||||
"receiver_node_id": tel.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"node_id": tel.node_id,
|
||||
"node_public_key": tel.node_public_key,
|
||||
"parsed_data": tel.parsed_data,
|
||||
"received_at": tel.received_at,
|
||||
"created_at": tel.created_at,
|
||||
}
|
||||
items.append(TelemetryRead(**data))
|
||||
|
||||
return TelemetryList(
|
||||
items=[TelemetryRead.model_validate(t) for t in records],
|
||||
items=items,
|
||||
total=total,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
@@ -62,10 +89,26 @@ async def get_telemetry(
|
||||
telemetry_id: str,
|
||||
) -> TelemetryRead:
|
||||
"""Get a single telemetry record by ID."""
|
||||
query = select(Telemetry).where(Telemetry.id == telemetry_id)
|
||||
telemetry = session.execute(query).scalar_one_or_none()
|
||||
ReceiverNode = aliased(Node)
|
||||
query = (
|
||||
select(Telemetry, ReceiverNode.public_key.label("receiver_pk"))
|
||||
.outerjoin(ReceiverNode, Telemetry.receiver_node_id == ReceiverNode.id)
|
||||
.where(Telemetry.id == telemetry_id)
|
||||
)
|
||||
result = session.execute(query).one_or_none()
|
||||
|
||||
if not telemetry:
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Telemetry record not found")
|
||||
|
||||
return TelemetryRead.model_validate(telemetry)
|
||||
tel, receiver_pk = result
|
||||
data = {
|
||||
"id": tel.id,
|
||||
"receiver_node_id": tel.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"node_id": tel.node_id,
|
||||
"node_public_key": tel.node_public_key,
|
||||
"parsed_data": tel.parsed_data,
|
||||
"received_at": tel.received_at,
|
||||
"created_at": tel.created_at,
|
||||
}
|
||||
return TelemetryRead(**data)
|
||||
|
||||
@@ -5,10 +5,11 @@ from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from meshcore_hub.api.auth import RequireRead
|
||||
from meshcore_hub.api.dependencies import DbSession
|
||||
from meshcore_hub.common.models import TracePath
|
||||
from meshcore_hub.common.models import Node, TracePath
|
||||
from meshcore_hub.common.schemas.messages import TracePathList, TracePathRead
|
||||
|
||||
router = APIRouter()
|
||||
@@ -18,14 +19,25 @@ router = APIRouter()
|
||||
async def list_trace_paths(
|
||||
_: RequireRead,
|
||||
session: DbSession,
|
||||
receiver_public_key: Optional[str] = Query(
|
||||
None, description="Filter by receiver node public key"
|
||||
),
|
||||
since: Optional[datetime] = Query(None, description="Start timestamp"),
|
||||
until: Optional[datetime] = Query(None, description="End timestamp"),
|
||||
limit: int = Query(50, ge=1, le=100, description="Page size"),
|
||||
offset: int = Query(0, ge=0, description="Page offset"),
|
||||
) -> TracePathList:
|
||||
"""List trace paths with filtering and pagination."""
|
||||
# Build query
|
||||
query = select(TracePath)
|
||||
# Alias for receiver node join
|
||||
ReceiverNode = aliased(Node)
|
||||
|
||||
# Build query with receiver node join
|
||||
query = select(TracePath, ReceiverNode.public_key.label("receiver_pk")).outerjoin(
|
||||
ReceiverNode, TracePath.receiver_node_id == ReceiverNode.id
|
||||
)
|
||||
|
||||
if receiver_public_key:
|
||||
query = query.where(ReceiverNode.public_key == receiver_public_key)
|
||||
|
||||
if since:
|
||||
query = query.where(TracePath.received_at >= since)
|
||||
@@ -41,10 +53,29 @@ async def list_trace_paths(
|
||||
query = query.order_by(TracePath.received_at.desc()).offset(offset).limit(limit)
|
||||
|
||||
# Execute
|
||||
trace_paths = session.execute(query).scalars().all()
|
||||
results = session.execute(query).all()
|
||||
|
||||
# Build response with receiver_public_key
|
||||
items = []
|
||||
for tp, receiver_pk in results:
|
||||
data = {
|
||||
"id": tp.id,
|
||||
"receiver_node_id": tp.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"initiator_tag": tp.initiator_tag,
|
||||
"path_len": tp.path_len,
|
||||
"flags": tp.flags,
|
||||
"auth": tp.auth,
|
||||
"path_hashes": tp.path_hashes,
|
||||
"snr_values": tp.snr_values,
|
||||
"hop_count": tp.hop_count,
|
||||
"received_at": tp.received_at,
|
||||
"created_at": tp.created_at,
|
||||
}
|
||||
items.append(TracePathRead(**data))
|
||||
|
||||
return TracePathList(
|
||||
items=[TracePathRead.model_validate(t) for t in trace_paths],
|
||||
items=items,
|
||||
total=total,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
@@ -58,10 +89,30 @@ async def get_trace_path(
|
||||
trace_path_id: str,
|
||||
) -> TracePathRead:
|
||||
"""Get a single trace path by ID."""
|
||||
query = select(TracePath).where(TracePath.id == trace_path_id)
|
||||
trace_path = session.execute(query).scalar_one_or_none()
|
||||
ReceiverNode = aliased(Node)
|
||||
query = (
|
||||
select(TracePath, ReceiverNode.public_key.label("receiver_pk"))
|
||||
.outerjoin(ReceiverNode, TracePath.receiver_node_id == ReceiverNode.id)
|
||||
.where(TracePath.id == trace_path_id)
|
||||
)
|
||||
result = session.execute(query).one_or_none()
|
||||
|
||||
if not trace_path:
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Trace path not found")
|
||||
|
||||
return TracePathRead.model_validate(trace_path)
|
||||
tp, receiver_pk = result
|
||||
data = {
|
||||
"id": tp.id,
|
||||
"receiver_node_id": tp.receiver_node_id,
|
||||
"receiver_public_key": receiver_pk,
|
||||
"initiator_tag": tp.initiator_tag,
|
||||
"path_len": tp.path_len,
|
||||
"flags": tp.flags,
|
||||
"auth": tp.auth,
|
||||
"path_hashes": tp.path_hashes,
|
||||
"snr_values": tp.snr_values,
|
||||
"hop_count": tp.hop_count,
|
||||
"received_at": tp.received_at,
|
||||
"created_at": tp.created_at,
|
||||
}
|
||||
return TracePathRead(**data)
|
||||
|
||||
@@ -13,10 +13,16 @@ class MessageRead(BaseModel):
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
message_type: str = Field(..., description="Message type (contact, channel)")
|
||||
pubkey_prefix: Optional[str] = Field(
|
||||
default=None, description="Sender's public key prefix (12 chars)"
|
||||
)
|
||||
sender_name: Optional[str] = Field(
|
||||
default=None, description="Sender's advertised node name"
|
||||
)
|
||||
sender_friendly_name: Optional[str] = Field(
|
||||
default=None, description="Sender's friendly name from node tags"
|
||||
)
|
||||
@@ -83,6 +89,9 @@ class AdvertisementRead(BaseModel):
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
node_id: Optional[str] = Field(default=None, description="Advertised node UUID")
|
||||
public_key: str = Field(..., description="Advertised public key")
|
||||
name: Optional[str] = Field(default=None, description="Advertised name")
|
||||
@@ -111,6 +120,9 @@ class TracePathRead(BaseModel):
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
initiator_tag: int = Field(..., description="Trace identifier")
|
||||
path_len: Optional[int] = Field(default=None, description="Path length")
|
||||
flags: Optional[int] = Field(default=None, description="Trace flags")
|
||||
@@ -145,6 +157,9 @@ class TelemetryRead(BaseModel):
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
node_id: Optional[str] = Field(default=None, description="Reporting node UUID")
|
||||
node_public_key: str = Field(..., description="Reporting node public key")
|
||||
parsed_data: Optional[dict] = Field(
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<th>Type</th>
|
||||
<th>From/Channel</th>
|
||||
<th>Message</th>
|
||||
<th>Receiver</th>
|
||||
<th>SNR</th>
|
||||
<th>Hops</th>
|
||||
</tr>
|
||||
@@ -78,8 +79,8 @@
|
||||
{% if msg.message_type == 'channel' %}
|
||||
<span class="font-mono">CH{{ msg.channel_idx }}</span>
|
||||
{% else %}
|
||||
{% if msg.sender_friendly_name %}
|
||||
<span class="font-medium">{{ msg.sender_friendly_name }}</span>
|
||||
{% if msg.sender_friendly_name or msg.sender_name %}
|
||||
<span class="font-medium">{{ msg.sender_friendly_name or msg.sender_name }}</span>
|
||||
{% else %}
|
||||
<span class="font-mono text-xs">{{ (msg.pubkey_prefix or '-')[:12] }}</span>
|
||||
{% endif %}
|
||||
@@ -88,6 +89,13 @@
|
||||
<td class="truncate-cell" title="{{ msg.text }}">
|
||||
{{ msg.text or '-' }}
|
||||
</td>
|
||||
<td class="text-xs">
|
||||
{% if msg.receiver_public_key %}
|
||||
<span class="font-mono" title="{{ msg.receiver_public_key }}">{{ msg.receiver_public_key[:8] }}...</span>
|
||||
{% else %}
|
||||
<span class="opacity-50">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if msg.snr is not none %}
|
||||
<span class="badge badge-ghost badge-sm">{{ "%.1f"|format(msg.snr) }}</span>
|
||||
@@ -105,7 +113,7 @@
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-8 opacity-70">No messages found.</td>
|
||||
<td colspan="7" class="text-center py-8 opacity-70">No messages found.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -97,7 +97,9 @@
|
||||
{% for ad in stats.recent_advertisements %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="font-medium">{{ ad.friendly_name or ad.name or ad.public_key[:12] + '...' }}</div>
|
||||
<a href="/nodes/{{ ad.public_key }}" class="link link-hover">
|
||||
<div class="font-medium">{{ ad.friendly_name or ad.name or ad.public_key[:12] + '...' }}</div>
|
||||
</a>
|
||||
{% if ad.friendly_name or ad.name %}
|
||||
<div class="text-xs opacity-50 font-mono">{{ ad.public_key[:12] }}...</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
<th>Time</th>
|
||||
<th>Type</th>
|
||||
<th>Name</th>
|
||||
<th>Receiver</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -111,6 +112,13 @@
|
||||
<td class="text-xs">{{ adv.received_at[:19].replace('T', ' ') if adv.received_at else '-' }}</td>
|
||||
<td>{{ adv.adv_type or '-' }}</td>
|
||||
<td>{{ adv.name or '-' }}</td>
|
||||
<td class="text-xs">
|
||||
{% if adv.receiver_public_key %}
|
||||
<span class="font-mono" title="{{ adv.receiver_public_key }}">{{ adv.receiver_public_key[:8] }}...</span>
|
||||
{% else %}
|
||||
<span class="opacity-50">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -133,6 +141,7 @@
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Data</th>
|
||||
<th>Receiver</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -146,6 +155,13 @@
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-xs">
|
||||
{% if tel.receiver_public_key %}
|
||||
<span class="font-mono" title="{{ tel.receiver_public_key }}">{{ tel.receiver_public_key[:8] }}...</span>
|
||||
{% else %}
|
||||
<span class="opacity-50">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user