From 304d35c057935aa1bfbb3c31ec8c6d8487d98258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 5 Oct 2023 01:36:24 +0200 Subject: [PATCH] live mesh log --- .../control/fragment_mesh_websocket.html | 66 +++++++++++++++++++ .../control/templates/control/mesh_logs.html | 19 ++++++ .../control/templates/control/mesh_nodes.html | 1 + src/c3nav/control/urls.py | 3 +- src/c3nav/control/views/mesh.py | 13 +++- src/c3nav/mesh/consumers.py | 49 +++++++++++--- src/c3nav/mesh/urls.py | 3 +- 7 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 src/c3nav/control/templates/control/fragment_mesh_websocket.html create mode 100644 src/c3nav/control/templates/control/mesh_logs.html diff --git a/src/c3nav/control/templates/control/fragment_mesh_websocket.html b/src/c3nav/control/templates/control/fragment_mesh_websocket.html new file mode 100644 index 00000000..d9b36c6d --- /dev/null +++ b/src/c3nav/control/templates/control/fragment_mesh_websocket.html @@ -0,0 +1,66 @@ +{% if node_names %} + {{ node_names|json_script:"node-names" }} +{% endif %} + \ No newline at end of file diff --git a/src/c3nav/control/templates/control/mesh_logs.html b/src/c3nav/control/templates/control/mesh_logs.html new file mode 100644 index 00000000..2c6ee496 --- /dev/null +++ b/src/c3nav/control/templates/control/mesh_logs.html @@ -0,0 +1,19 @@ +{% extends 'control/base.html' %} +{% load i18n %} + +{% block heading %}{% trans 'Mesh logs' %}{% endblock %} + +{% block subcontent %} + + + + + + + + + + +
{% trans 'Time' %}{% trans 'Uplink' %}{% trans 'Node' %}{% trans 'Message' %}
+ {% include "control/fragment_mesh_websocket.html" %} +{% endblock %} diff --git a/src/c3nav/control/templates/control/mesh_nodes.html b/src/c3nav/control/templates/control/mesh_nodes.html index 1cea8bed..6c613e65 100644 --- a/src/c3nav/control/templates/control/mesh_nodes.html +++ b/src/c3nav/control/templates/control/mesh_nodes.html @@ -41,4 +41,5 @@ {% endfor %} + {% include "control/fragment_mesh_websocket.html" %} {% endblock %} diff --git a/src/c3nav/control/urls.py b/src/c3nav/control/urls.py index 9c027ff2..17e514d3 100644 --- a/src/c3nav/control/urls.py +++ b/src/c3nav/control/urls.py @@ -1,7 +1,7 @@ from django.urls import path from c3nav.control.views.mesh import MeshNodeListView, MeshMessageListView, MeshNodeDetailView, MeshMessageSendView, \ - MeshNodeEditView + MeshNodeEditView, MeshLogView from c3nav.control.views.mapupdates import map_updates from c3nav.control.views.announcements import announcement_list, announcement_detail from c3nav.control.views.access import grant_access, grant_access_qr @@ -17,6 +17,7 @@ urlpatterns = [ path('announcements//', announcement_detail, name='control.announcements.detail'), path('mapupdates/', map_updates, name='control.map_updates'), path('mesh/', MeshNodeListView.as_view(), name='control.mesh_nodes'), + path('mesh/logs/', MeshLogView.as_view(), name='control.mesh_log'), path('mesh/messages/', MeshMessageListView.as_view(), name='control.mesh_messages'), path('mesh//', MeshNodeDetailView.as_view(), name='control.mesh_node.detail'), path('mesh//edit/', MeshNodeEditView.as_view(), name='control.mesh_node.edit'), diff --git a/src/c3nav/control/views/mesh.py b/src/c3nav/control/views/mesh.py index b6da40bf..3a99fa1f 100644 --- a/src/c3nav/control/views/mesh.py +++ b/src/c3nav/control/views/mesh.py @@ -4,7 +4,7 @@ from django.db.models import Max from django.http import Http404 from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import ListView, DetailView, FormView, UpdateView +from django.views.generic import ListView, DetailView, FormView, UpdateView, TemplateView from c3nav.control.forms import MeshMessageFilterForm from c3nav.control.views.base import ControlPanelMixin @@ -116,3 +116,14 @@ class MeshMessageSendView(ControlPanelMixin, FormView): form.send() messages.success(self.request, _('Message sent successfully')) return super().form_valid(form) + +class MeshLogView(ControlPanelMixin, TemplateView): + template_name = "control/mesh_logs.html" + + def get_context_data(self, **kwargs): + return { + **super().get_context_data(), + "node_names": { + node.address: node.name for node in MeshNode.objects.all() + } + } \ No newline at end of file diff --git a/src/c3nav/mesh/consumers.py b/src/c3nav/mesh/consumers.py index 2e435218..27fd4648 100644 --- a/src/c3nav/mesh/consumers.py +++ b/src/c3nav/mesh/consumers.py @@ -1,7 +1,7 @@ import traceback from asgiref.sync import async_to_sync -from channels.generic.websocket import WebsocketConsumer +from channels.generic.websocket import WebsocketConsumer, JsonWebsocketConsumer from django.utils import timezone from c3nav.mesh.utils import get_mesh_comm_group @@ -13,14 +13,14 @@ from c3nav.mesh.models import MeshNode, NodeMessage # noinspection PyAttributeOutsideInit class MeshConsumer(WebsocketConsumer): def connect(self): - print('connected!') # todo: auth self.uplink_node = None + self.log_text(None, "new mesh websocket connection") self.dst_nodes = set() self.accept() def disconnect(self, close_code): - print('disconnected!') + self.log_text(self.uplink_node, "mesh websocket disconnected") if self.uplink_node is not None: # leave broadcast group async_to_sync(self.channel_layer.group_add)(get_mesh_comm_group(BROADCAST_ADDRESS), self.channel_name) @@ -29,7 +29,8 @@ class MeshConsumer(WebsocketConsumer): self.remove_dst_nodes(self.dst_nodes) def send_msg(self, msg): - print('Sending message:', msg) + print("sending", msg) + self.log_text(msg.dst, "sending %s" % msg) self.send(bytes_data=msg.encode()) def receive(self, text_data=None, bytes_data=None): @@ -92,19 +93,20 @@ class MeshConsumer(WebsocketConsumer): def mesh_uplink_consumer(self, data): # message handler: if we are not the given uplink, leave this group if data["name"] != self.channel_name: - print('shutting down since we have been replaced') + self.log_text(self.uplink_node, "shutting down, uplink now served by new consumer") self.close() def mesh_dst_node_uplink(self, data): # message handler: if we are not the given uplink, leave this group if data["uplink"] != self.uplink_node.address: - print('leaving node group...') + self.log_text(data["address"], "node now served by new consumer") self.remove_dst_nodes((data["address"], )) def mesh_send(self, data): self.send_msg(MeshMessage.fromjson(data["msg"])) def log_received_message(self, src_node: MeshNode, msg: messages.MeshMessage): + self.log_text(msg.src, "received %s" % msg) NodeMessage.objects.create( uplink_node=self.uplink_node, src_node=src_node, @@ -112,10 +114,22 @@ class MeshConsumer(WebsocketConsumer): data=msg.tojson() ) + def log_text(self, address, text): + address = getattr(address, 'address', address) + async_to_sync(self.channel_layer.group_send)("mesh_log", { + "type": "mesh.log_entry", + "timestamp": timezone.now().strftime("%d.%m.%y %H:%M:%S.%f"), + "channel": self.channel_name, + "uplink": self.uplink_node.address if self.uplink_node else None, + "node": address, + "text": text, + }) + def add_dst_nodes(self, nodes=None, addresses=None): nodes = list(nodes) if nodes else [] addresses = set(addresses) if addresses else set() + node_addresses = set(node.address for node in nodes) missing_addresses = addresses - set(node.address for node in nodes) if missing_addresses: @@ -123,9 +137,13 @@ class MeshConsumer(WebsocketConsumer): [MeshNode(address=address) for address in missing_addresses], ignore_conflicts=True ) + + addresses |= node_addresses addresses |= missing_addresses for address in addresses: + self.log_text(address, "destination added") + # create group name for this address group = get_mesh_comm_group(address) @@ -157,6 +175,8 @@ class MeshConsumer(WebsocketConsumer): def remove_dst_nodes(self, addresses): for address in tuple(addresses): + self.log_text(address, "destination removed") + # create group name for this address group = get_mesh_comm_group(address) @@ -171,5 +191,18 @@ class MeshConsumer(WebsocketConsumer): uplink_id=None, ) - def remove_route(self, route_address): - MeshNode.objects.filter(route_id=route_address).update(route_id=None) + +class MeshUIConsumer(JsonWebsocketConsumer): + def connect(self): + # todo: auth + self.accept() + + def receive_json(self, content, **kwargs): + if content.get("subscribe", None) == "log": + async_to_sync(self.channel_layer.group_add)("mesh_log", self.channel_name) + + def mesh_log_entry(self, data): + self.send_json(data) + + def disconnect(self, code): + async_to_sync(self.channel_layer.group_discard)("mesh_log", self.channel_name) \ No newline at end of file diff --git a/src/c3nav/mesh/urls.py b/src/c3nav/mesh/urls.py index 4d075a86..2b899351 100644 --- a/src/c3nav/mesh/urls.py +++ b/src/c3nav/mesh/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from c3nav.mesh.consumers import MeshConsumer +from c3nav.mesh.consumers import MeshConsumer, MeshUIConsumer websocket_urlpatterns = [ path('ws', MeshConsumer.as_asgi()), + path('ui/ws', MeshUIConsumer.as_asgi()), ]