sending updates

This commit is contained in:
Laura Klünder 2023-10-05 04:05:29 +02:00
parent 40c4e98816
commit c724833542
9 changed files with 154 additions and 20 deletions

View file

@ -1,25 +1,33 @@
{% if node_names %} {% if node_names %}
{{ node_names|json_script:"node-names" }} {{ node_names|json_script:"node-names" }}
{% endif %} {% endif %}
{% if send_uuid %}
{{ send_uuid|json_script:"send-uuid" }}
{% endif %}
<script type="text/javascript"> <script type="text/javascript">
{% if node_names %} {% if node_names %}
const node_names = JSON.parse(document.getElementById('node-names').textContent); const node_names = JSON.parse(document.getElementById('node-names').textContent);
{% endif %} {% endif %}
function connect() { function connect() {
console.log('reconnecting websocket...'); console.log('reconnecting websocket...');
var ws = new WebSocket((window.location.protocol=="https:"?"wss:":"ws:")+window.location.host+"/mesh/ui/ws", []); var ws = new WebSocket((window.location.protocol=="https:"?"wss:":"ws:")+window.location.host+"/mesh/ui/ws", []);
ws.onopen = (event) => { ws.onopen = (event) => {
console.log('websocket connected.'); console.log('websocket connected.');
ws.send(JSON.stringify({"subscribe": "log"})); {% if send_uuid %}
ws.send(JSON.stringify({"send_msg": JSON.parse(document.getElementById('send-uuid').textContent)}));
{% else %}
ws.send(JSON.stringify({"subscribe": "log"}));
{% endif %}
}; };
ws.onclose = (event) => { ws.onclose = (event) => {
window.setTimeout(connect, 500); window.setTimeout(connect, 500);
} }
ws.onmessage = (event) => { ws.onmessage = (event) => {
var data = JSON.parse(event.data); var data = JSON.parse(event.data), line, text;
switch(data.type) { switch(data.type) {
case 'mesh.log_entry': case 'mesh.log_entry':
var line = document.createElement("tr"), cell, link_tag; line = document.createElement("tr"), cell, link_tag;
cell = document.createElement("td"); cell = document.createElement("td");
cell.innerText = data.timestamp; cell.innerText = data.timestamp;
@ -54,6 +62,16 @@ function connect() {
line.appendChild(cell); line.appendChild(cell);
document.querySelector("tbody").prepend(line); document.querySelector("tbody").prepend(line);
break
case 'mesh.msg_sent':
{% if send_uuid %}
line = document.createElement("p");
line.innerText = "sent via uplink ["+data.channel+"] "+data.uplink;
if (node_names[data.uplink]) {
line.innerText += " ("+node_names[data.uplink]+")";
}
document.getElementById("sending-status-"+data.recipient).appendChild(line);
{% endif %}
break; break;
} }

View file

@ -17,6 +17,7 @@
<form method="POST" style="max-width:400px;"> <form method="POST" style="max-width:400px;">
{% csrf_token %} {% csrf_token %}
{{ form }} {{ form }}
<noscript><input type="hidden" name="noscript" value"1"></noscript>
<button type="submit">{% trans 'Send' %}</button> <button type="submit">{% trans 'Send' %}</button>
</form> </form>

View file

@ -0,0 +1,26 @@
{% extends 'control/base.html' %}
{% load i18n %}
{% block heading %}
{% blocktrans trimmed with msg_type=msg_type %}
Sending {{ msg_type }} message
{% endblocktrans %}
{% endblock %}
{% block subcontent %}
<p><a class="button" href="{{ success_url }}">Go back</a></p>
<table>
<tr>
<th>Recipient</th>
<th>Status</th>
</tr>
{% for address, name in recipients %}
<tr>
<td>{{ address }}{% if name %} ({{ name }}){% endif %}</td>
<td id="sending-status-{{ address }}"></td>
</tr>
{% endfor %}
</table>
{% include "control/fragment_mesh_websocket.html" %}
{% endblock %}

View file

@ -41,5 +41,4 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% include "control/fragment_mesh_websocket.html" %}
{% endblock %} {% endblock %}

View file

@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from c3nav.control.views.mesh import MeshNodeListView, MeshMessageListView, MeshNodeDetailView, MeshMessageSendView, \ from c3nav.control.views.mesh import MeshNodeListView, MeshMessageListView, MeshNodeDetailView, MeshMessageSendView, \
MeshNodeEditView, MeshLogView MeshNodeEditView, MeshLogView, MeshMessageSendingView
from c3nav.control.views.mapupdates import map_updates from c3nav.control.views.mapupdates import map_updates
from c3nav.control.views.announcements import announcement_list, announcement_detail from c3nav.control.views.announcements import announcement_list, announcement_detail
from c3nav.control.views.access import grant_access, grant_access_qr from c3nav.control.views.access import grant_access, grant_access_qr
@ -21,6 +21,8 @@ urlpatterns = [
path('mesh/messages/', MeshMessageListView.as_view(), name='control.mesh_messages'), path('mesh/messages/', MeshMessageListView.as_view(), name='control.mesh_messages'),
path('mesh/<str:pk>/', MeshNodeDetailView.as_view(), name='control.mesh_node.detail'), path('mesh/<str:pk>/', MeshNodeDetailView.as_view(), name='control.mesh_node.detail'),
path('mesh/<str:pk>/edit/', MeshNodeEditView.as_view(), name='control.mesh_node.edit'), path('mesh/<str:pk>/edit/', MeshNodeEditView.as_view(), name='control.mesh_node.edit'),
path('mesh/<str:recipient>/message/<str:msg_type>/', MeshMessageSendView.as_view(), name='control.mesh_message.send'), path('mesh/message/sending/<uuid:uuid>/', MeshMessageSendingView.as_view(), name='control.mesh_message.sending'),
path('mesh/message/<str:recipient>/<str:msg_type>/', MeshMessageSendView.as_view(), name='control.mesh_message.send'),
path('mesh/message/<str:msg_type>/', MeshMessageSendView.as_view(), name='control.mesh_message.send'),
path('', ControlPanelIndexView.as_view(), name='control.index'), path('', ControlPanelIndexView.as_view(), name='control.index'),
] ]

View file

@ -1,7 +1,10 @@
from uuid import uuid4
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Max from django.db.models import Max
from django.http import Http404 from django.http import Http404
from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView, DetailView, FormView, UpdateView, TemplateView from django.views.generic import ListView, DetailView, FormView, UpdateView, TemplateView
@ -113,9 +116,39 @@ class MeshMessageSendView(ControlPanelMixin, FormView):
return self.request.path return self.request.path
def form_valid(self, form): def form_valid(self, form):
form.send() if 'noscript' in self.request.POST:
messages.success(self.request, _('Message sent successfully')) form.send()
return super().form_valid(form) messages.success(self.request, _('Message sent successfully(?)'))
super().form_valid(form)
uuid = uuid4()
self.request.session["mesh_msg_%s" % uuid] = {
"success_url": self.get_success_url(),
"recipients": form.get_recipients(),
"msg_data": form.get_msg_data(),
}
return redirect(reverse('control.mesh_message.sending', kwargs={'uuid': uuid}))
class MeshMessageSendingView(ControlPanelMixin, TemplateView):
template_name = "control/mesh_message_sending.html"
def get_context_data(self, uuid):
try:
data = self.request.session["mesh_msg_%s" % uuid]
except KeyError:
raise Http404
node_names = {
node.address: node.name for node in MeshNode.objects.all()
}
return {
**super().get_context_data(),
"node_names": node_names,
"send_uuid": uuid,
**data,
"recipients": [(address, node_names[address]) for address in data["recipients"]],
"msg_type": MeshMessageType(data["msg_data"]["msg_id"]).name,
}
class MeshLogView(ControlPanelMixin, TemplateView): class MeshLogView(ControlPanelMixin, TemplateView):
template_name = "control/mesh_logs.html" template_name = "control/mesh_logs.html"

View file

@ -28,10 +28,19 @@ class MeshConsumer(WebsocketConsumer):
# remove all other destinations # remove all other destinations
self.remove_dst_nodes(self.dst_nodes) self.remove_dst_nodes(self.dst_nodes)
def send_msg(self, msg): def send_msg(self, msg, sender=None):
# print("sending", msg) # print("sending", msg)
# self.log_text(msg.dst, "sending %s" % msg) # self.log_text(msg.dst, "sending %s" % msg)
self.send(bytes_data=msg.encode()) self.send(bytes_data=msg.encode())
async_to_sync(self.channel_layer.group_send)("mesh_msg_sent", {
"type": "mesh.msg_sent",
"timestamp": timezone.now().strftime("%d.%m.%y %H:%M:%S.%f"),
"channel": self.channel_name,
"sender": sender,
"uplink": self.uplink_node.address if self.uplink_node else None,
"recipient": msg.dst,
#"msg": msg.tojson(), # not doing this part for privacy reasons
})
def receive(self, text_data=None, bytes_data=None): def receive(self, text_data=None, bytes_data=None):
if bytes_data is None: if bytes_data is None:
@ -103,15 +112,23 @@ class MeshConsumer(WebsocketConsumer):
self.remove_dst_nodes((data["address"], )) self.remove_dst_nodes((data["address"], ))
def mesh_send(self, data): def mesh_send(self, data):
self.send_msg(MeshMessage.fromjson(data["msg"])) print("mesh_send", data)
self.send_msg(MeshMessage.fromjson(data["msg"]), data["sender"])
def log_received_message(self, src_node: MeshNode, msg: messages.MeshMessage): def log_received_message(self, src_node: MeshNode, msg: messages.MeshMessage):
# self.log_text(msg.src, "received %s" % msg) as_json = msg.tojson()
async_to_sync(self.channel_layer.group_send)("mesh_msg_received", {
"type": "mesh.msg_received",
"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,
"msg": as_json,
})
NodeMessage.objects.create( NodeMessage.objects.create(
uplink_node=self.uplink_node, uplink_node=self.uplink_node,
src_node=src_node, src_node=src_node,
message_type=msg.msg_id, message_type=msg.msg_id,
data=msg.tojson() data=as_json,
) )
def log_text(self, address, text): def log_text(self, address, text):
@ -196,13 +213,47 @@ class MeshUIConsumer(JsonWebsocketConsumer):
def connect(self): def connect(self):
# todo: auth # todo: auth
self.accept() self.accept()
self.msg_sent_filter = {}
self.msg_received_filter = {}
def receive_json(self, content, **kwargs): def receive_json(self, content, **kwargs):
if content.get("subscribe", None) == "log": if content.get("subscribe", None) == "log":
async_to_sync(self.channel_layer.group_add)("mesh_log", self.channel_name) async_to_sync(self.channel_layer.group_add)("mesh_log", self.channel_name)
if content.get("subscribe", None) == "msg_sent":
async_to_sync(self.channel_layer.group_add)("mesh_msg_sent", self.channel_name)
self.msg_sent_filter = dict(content.get("filter", {}))
if content.get("subscribe", None) == "msg_received":
async_to_sync(self.channel_layer.group_add)("mesh_msg_sent", self.channel_name)
self.msg_received_filter = dict(content.get("filter", {}))
if "send_msg" in content:
msg_to_send = self.scope["session"].pop("mesh_msg_%s" % content["send_msg"], None)
if not msg_to_send:
return
self.scope["session"].save()
async_to_sync(self.channel_layer.group_add)("mesh_msg_sent", self.channel_name)
self.msg_sent_filter = {"sender": self.channel_name}
for recipient in msg_to_send["recipients"]:
print('send to', recipient)
MeshMessage.fromjson({
'dst': recipient,
**msg_to_send["msg_data"],
}).send(sender=self.channel_name)
def mesh_log_entry(self, data): def mesh_log_entry(self, data):
self.send_json(data) self.send_json(data)
def mesh_msg_sent(self, data):
print('got data', data)
for key, value in self.msg_sent_filter.items():
if isinstance(value, list):
if data.get(key, None) not in value:
return
else:
if data.get(key, None) != value:
return
self.send_json(data)
def disconnect(self, code): def disconnect(self, code):
async_to_sync(self.channel_layer.group_discard)("mesh_log", self.channel_name) async_to_sync(self.channel_layer.group_discard)("mesh_log", self.channel_name)
async_to_sync(self.channel_layer.group_discard)("mesh_msg_sent", self.channel_name)
async_to_sync(self.channel_layer.group_discard)("mesh_msg_received", self.channel_name)

View file

@ -63,17 +63,22 @@ class MeshMessageForm(forms.Form):
msg_data.pop('recipients', None) msg_data.pop('recipients', None)
return msg_data return msg_data
def send(self): def get_msg_data(self):
if not self.is_valid(): if not self.is_valid():
raise Exception('nope') raise Exception('nope')
msg_data = { return {
'msg_id': self.msg_type, 'msg_id': self.msg_type,
'src': ROOT_ADDRESS, 'src': ROOT_ADDRESS,
**self.get_cleaned_msg_data(), **self.get_cleaned_msg_data(),
} }
recipients = [self.recipient] if self.recipient else self.cleaned_data['recipients'] def get_recipients(self):
return [self.recipient] if self.recipient else self.cleaned_data['recipients']
def send(self):
msg_data = self.get_msg_data()
recipients = self.get_recipients()
for recipient in recipients: for recipient in recipients:
print('sending to ', recipient) print('sending to ', recipient)
MeshMessage.fromjson({ MeshMessage.fromjson({

View file

@ -16,7 +16,6 @@ PARENT_ADDRESS = '00:00:00:ff:ff:ff'
BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff' BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff'
NO_LAYER = 0xFF NO_LAYER = 0xFF
@unique @unique
class MeshMessageType(IntEnum): class MeshMessageType(IntEnum):
NOOP = 0x00 NOOP = 0x00
@ -94,7 +93,6 @@ class MeshMessage:
kwargs = data.copy() kwargs = data.copy()
klass = cls.msg_types[kwargs.pop('msg_id')] klass = cls.msg_types[kwargs.pop('msg_id')]
kwargs = klass.upgrade_json(kwargs) kwargs = klass.upgrade_json(kwargs)
names = set(field.name for field in fields(klass))
for field_ in fields(klass): for field_ in fields(klass):
if is_dataclass(field_.type): if is_dataclass(field_.type):
kwargs[field_.name] = field_.type.fromjson(kwargs[field_.name]) kwargs[field_.name] = field_.type.fromjson(kwargs[field_.name])
@ -104,9 +102,10 @@ class MeshMessage:
def upgrade_json(cls, data): def upgrade_json(cls, data):
return data return data
def send(self): def send(self, sender=None):
async_to_sync(channels.layers.get_channel_layer().group_send)(get_mesh_comm_group(self.dst), { async_to_sync(channels.layers.get_channel_layer().group_send)(get_mesh_comm_group(self.dst), {
"type": "mesh.send", "type": "mesh.send",
"sender": sender,
"msg": self.tojson() "msg": self.tojson()
}) })