Messages list: swap Time/Type columns, add receiver node links

- Swap Time and Type columns (Type now first)
- Add receiver_name and receiver_friendly_name to MessageRead schema
- Update messages API to fetch receiver node names and tags
- Make Receiver column a link showing name with public key prefix
This commit is contained in:
Claude
2025-12-05 18:09:29 +00:00
parent 5077178a6d
commit 087a3c4c43
3 changed files with 61 additions and 11 deletions
+44 -5
View File
@@ -5,7 +5,7 @@ from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from sqlalchemy import func, select
from sqlalchemy.orm import aliased
from sqlalchemy.orm import aliased, selectinload
from meshcore_hub.api.auth import RequireRead
from meshcore_hub.api.dependencies import DbSession
@@ -15,6 +15,16 @@ from meshcore_hub.common.schemas.messages import MessageList, MessageRead
router = APIRouter()
def _get_friendly_name(node: Optional[Node]) -> Optional[str]:
"""Extract friendly_name tag from a node's tags."""
if not node or not node.tags:
return None
for tag in node.tags:
if tag.key == "friendly_name":
return tag.value
return None
@router.get("", response_model=MessageList)
async def list_messages(
_: RequireRead,
@@ -36,9 +46,12 @@ async def list_messages(
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
)
query = select(
Message,
ReceiverNode.public_key.label("receiver_pk"),
ReceiverNode.name.label("receiver_name"),
ReceiverNode.id.label("receiver_id"),
).outerjoin(ReceiverNode, Message.receiver_node_id == ReceiverNode.id)
if message_type:
query = query.where(Message.message_type == message_type)
@@ -96,13 +109,39 @@ async def list_messages(
for public_key, value in session.execute(friendly_name_query).all():
friendly_names[public_key[:12]] = value
# Collect receiver node IDs to fetch tags
receiver_ids = set()
for row in results:
if row.receiver_id:
receiver_ids.add(row.receiver_id)
# Fetch receiver nodes with tags
receivers_by_id: dict[str, Node] = {}
if receiver_ids:
receivers_query = (
select(Node)
.where(Node.id.in_(receiver_ids))
.options(selectinload(Node.tags))
)
receivers = session.execute(receivers_query).scalars().all()
receivers_by_id = {n.id: n for n in receivers}
# Build response with sender info and received_by
items = []
for m, receiver_pk in results:
for row in results:
m = row[0]
receiver_pk = row.receiver_pk
receiver_name = row.receiver_name
receiver_node = (
receivers_by_id.get(row.receiver_id) if row.receiver_id else None
)
msg_dict = {
"id": m.id,
"receiver_node_id": m.receiver_node_id,
"received_by": receiver_pk,
"receiver_name": receiver_name,
"receiver_friendly_name": _get_friendly_name(receiver_node),
"message_type": m.message_type,
"pubkey_prefix": m.pubkey_prefix,
"sender_name": (
@@ -12,6 +12,10 @@ class MessageRead(BaseModel):
received_by: Optional[str] = Field(
default=None, description="Receiving interface node public key"
)
receiver_name: Optional[str] = Field(default=None, description="Receiver node name")
receiver_friendly_name: Optional[str] = Field(
default=None, description="Receiver friendly name from tags"
)
message_type: str = Field(..., description="Message type (contact, channel)")
pubkey_prefix: Optional[str] = Field(
default=None, description="Sender's public key prefix (12 chars)"
+13 -6
View File
@@ -53,8 +53,8 @@
<table class="table table-zebra">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Time</th>
<th>From/Channel</th>
<th>Message</th>
<th>Receiver</th>
@@ -64,9 +64,6 @@
<tbody>
{% for msg in messages %}
<tr class="hover align-top">
<td class="text-xs whitespace-nowrap">
{{ msg.received_at[:19].replace('T', ' ') if msg.received_at else '-' }}
</td>
<td>
{% if msg.message_type == 'channel' %}
<span class="badge badge-info badge-sm">Channel</span>
@@ -74,6 +71,9 @@
<span class="badge badge-success badge-sm">Direct</span>
{% endif %}
</td>
<td class="text-sm whitespace-nowrap">
{{ msg.received_at[:19].replace('T', ' ') if msg.received_at else '-' }}
</td>
<td class="text-sm whitespace-nowrap">
{% if msg.message_type == 'channel' %}
<span class="font-mono">CH{{ msg.channel_idx }}</span>
@@ -86,9 +86,16 @@
{% endif %}
</td>
<td class="break-words max-w-md" style="white-space: pre-wrap;">{{ msg.text or '-' }}</td>
<td class="text-xs whitespace-nowrap">
<td>
{% if msg.received_by %}
<span class="font-mono" title="{{ msg.received_by }}">{{ msg.received_by[:8] }}...</span>
<a href="/nodes/{{ msg.received_by }}" class="link link-hover">
{% if msg.receiver_friendly_name or msg.receiver_name %}
<div class="font-medium">{{ msg.receiver_friendly_name or msg.receiver_name }}</div>
<div class="text-xs font-mono opacity-70">{{ msg.received_by[:16] }}...</div>
{% else %}
<span class="font-mono text-sm">{{ msg.received_by[:16] }}...</span>
{% endif %}
</a>
{% else %}
<span class="opacity-50">-</span>
{% endif %}