mirror of
https://github.com/craigerl/aprsd.git
synced 2025-09-02 13:17:54 -04:00
Merge pull request #101 from craigerl/webchat_mobile
Add support for mobile browsers for webchat
This commit is contained in:
commit
acecba27e8
1
Makefile
1
Makefile
@ -48,6 +48,7 @@ clean-test: ## remove test and coverage artifacts
|
|||||||
|
|
||||||
clean-dev:
|
clean-dev:
|
||||||
rm -rf $(VENVDIR)
|
rm -rf $(VENVDIR)
|
||||||
|
rm Makefile.venv
|
||||||
|
|
||||||
test: dev ## Run all the tox tests
|
test: dev ## Run all the tox tests
|
||||||
tox -p all
|
tox -p all
|
||||||
|
@ -2,15 +2,14 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import queue
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import aprslib
|
|
||||||
from aprslib import util as aprslib_util
|
from aprslib import util as aprslib_util
|
||||||
import click
|
import click
|
||||||
|
from device_detector import DeviceDetector
|
||||||
import flask
|
import flask
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask.logging import default_handler
|
from flask.logging import default_handler
|
||||||
@ -26,7 +25,6 @@ from aprsd import config as aprsd_config
|
|||||||
from aprsd import messaging, packets, stats, threads, utils
|
from aprsd import messaging, packets, stats, threads, utils
|
||||||
from aprsd.aprsd import cli
|
from aprsd.aprsd import cli
|
||||||
from aprsd.logging import rich as aprsd_logging
|
from aprsd.logging import rich as aprsd_logging
|
||||||
from aprsd.threads import aprsd as aprsd_thread
|
|
||||||
from aprsd.threads import rx
|
from aprsd.threads import rx
|
||||||
from aprsd.utils import objectstore, trace
|
from aprsd.utils import objectstore, trace
|
||||||
|
|
||||||
@ -34,14 +32,6 @@ from aprsd.utils import objectstore, trace
|
|||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
auth = HTTPBasicAuth()
|
auth = HTTPBasicAuth()
|
||||||
users = None
|
users = None
|
||||||
rx_msg_queue = queue.Queue(maxsize=20)
|
|
||||||
tx_msg_queue = queue.Queue(maxsize=20)
|
|
||||||
control_queue = queue.Queue(maxsize=20)
|
|
||||||
msg_queues = {
|
|
||||||
"rx": rx_msg_queue,
|
|
||||||
"control": control_queue,
|
|
||||||
"tx": tx_msg_queue,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
@ -142,62 +132,19 @@ def verify_password(username, password):
|
|||||||
|
|
||||||
|
|
||||||
class WebChatRXThread(rx.APRSDRXThread):
|
class WebChatRXThread(rx.APRSDRXThread):
|
||||||
"""Class that connects to aprsis/kiss and waits for messages."""
|
"""Class that connects to APRISIS/kiss and waits for messages.
|
||||||
|
|
||||||
|
After the packet is received from APRSIS/KISS, the packet is
|
||||||
|
sent to processing in the WebChatProcessPacketThread.
|
||||||
|
"""
|
||||||
|
def __init__(self, config, socketio):
|
||||||
|
super().__init__(None, config)
|
||||||
|
self.socketio = socketio
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
def connected(self, connected=True):
|
def connected(self, connected=True):
|
||||||
self.connected = connected
|
self.connected = connected
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.thread_stop = True
|
|
||||||
client.factory.create().client.stop()
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
# setup the consumer of messages and block until a messages
|
|
||||||
msg = None
|
|
||||||
try:
|
|
||||||
msg = self.msg_queues["tx"].get_nowait()
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
if msg:
|
|
||||||
LOG.debug("GOT msg from TX queue!!")
|
|
||||||
msg.send()
|
|
||||||
except (
|
|
||||||
aprslib.exceptions.ConnectionDrop,
|
|
||||||
aprslib.exceptions.ConnectionError,
|
|
||||||
):
|
|
||||||
LOG.error("Connection dropped, reconnecting")
|
|
||||||
# Put it back on the queue to send.
|
|
||||||
self.msg_queues["tx"].put(msg)
|
|
||||||
# Force the deletion of the client object connected to aprs
|
|
||||||
# This will cause a reconnect, next time client.get_client()
|
|
||||||
# is called
|
|
||||||
self._client.reset()
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# When new packets come in the consumer will process
|
|
||||||
# the packet
|
|
||||||
|
|
||||||
# This call blocks until thread stop() is called.
|
|
||||||
self._client.client.consumer(
|
|
||||||
self.process_packet, raw=False, blocking=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
except (
|
|
||||||
aprslib.exceptions.ConnectionDrop,
|
|
||||||
aprslib.exceptions.ConnectionError,
|
|
||||||
):
|
|
||||||
LOG.error("Connection dropped, reconnecting")
|
|
||||||
time.sleep(5)
|
|
||||||
# Force the deletion of the client object connected to aprs
|
|
||||||
# This will cause a reconnect, next time client.get_client()
|
|
||||||
# is called
|
|
||||||
self._client.reset()
|
|
||||||
return True
|
|
||||||
return True
|
|
||||||
|
|
||||||
def process_packet(self, *args, **kwargs):
|
def process_packet(self, *args, **kwargs):
|
||||||
# packet = self._client.decode_packet(*args, **kwargs)
|
# packet = self._client.decode_packet(*args, **kwargs)
|
||||||
if "packet" in kwargs:
|
if "packet" in kwargs:
|
||||||
@ -206,96 +153,55 @@ class WebChatRXThread(rx.APRSDRXThread):
|
|||||||
packet = self._client.decode_packet(*args, **kwargs)
|
packet = self._client.decode_packet(*args, **kwargs)
|
||||||
|
|
||||||
LOG.debug(f"GOT Packet {packet}")
|
LOG.debug(f"GOT Packet {packet}")
|
||||||
self.msg_queues["rx"].put(packet)
|
thread = WebChatProcessPacketThread(
|
||||||
|
config=self.config,
|
||||||
|
packet=packet,
|
||||||
|
socketio=self.socketio,
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
class WebChatTXThread(aprsd_thread.APRSDThread):
|
class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
|
||||||
"""Class that """
|
"""Class that handles packets being sent to us."""
|
||||||
def __init__(self, msg_queues, config, socketio):
|
def __init__(self, config, packet, socketio):
|
||||||
super().__init__("_TXThread_")
|
|
||||||
self.msg_queues = msg_queues
|
|
||||||
self.config = config
|
|
||||||
self.socketio = socketio
|
self.socketio = socketio
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
super().__init__(config, packet)
|
||||||
def loop(self):
|
|
||||||
try:
|
|
||||||
msg = self.msg_queues["control"].get_nowait()
|
|
||||||
self.connected = msg["connected"]
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
packet = self.msg_queues["rx"].get_nowait()
|
|
||||||
if packet:
|
|
||||||
# we got a packet and we need to send it to the
|
|
||||||
# web socket
|
|
||||||
self.process_packet(packet)
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.exception(ex)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def process_ack_packet(self, packet):
|
def process_ack_packet(self, packet):
|
||||||
|
super().process_ack_packet(packet)
|
||||||
ack_num = packet.get("msgNo")
|
ack_num = packet.get("msgNo")
|
||||||
LOG.info(f"We got ack for our sent message {ack_num}")
|
|
||||||
messaging.log_packet(packet)
|
|
||||||
SentMessages().ack(int(ack_num))
|
SentMessages().ack(int(ack_num))
|
||||||
self.socketio.emit(
|
self.socketio.emit(
|
||||||
"ack", SentMessages().get(int(ack_num)),
|
"ack", SentMessages().get(int(ack_num)),
|
||||||
namespace="/sendmsg",
|
namespace="/sendmsg",
|
||||||
)
|
)
|
||||||
stats.APRSDStats().ack_rx_inc()
|
|
||||||
self.got_ack = True
|
self.got_ack = True
|
||||||
|
|
||||||
def process_packet(self, packet):
|
def process_non_ack_packet(self, packet):
|
||||||
LOG.info(f"process PACKET {packet}")
|
LOG.info(f"process non ack PACKET {packet}")
|
||||||
tocall = packet.get("addresse", None)
|
packet.get("addresse", None)
|
||||||
fromcall = packet["from"]
|
fromcall = packet["from"]
|
||||||
msg = packet.get("message_text", None)
|
|
||||||
msg_id = packet.get("msgNo", "0")
|
|
||||||
msg_response = packet.get("response", None)
|
|
||||||
|
|
||||||
if tocall == self.config["aprsd"]["callsign"] and msg_response == "ack":
|
packets.PacketList().add(packet)
|
||||||
self.process_ack_packet(packet)
|
stats.APRSDStats().msgs_rx_inc()
|
||||||
elif tocall == self.config["aprsd"]["callsign"]:
|
message = packet.get("message_text", None)
|
||||||
messaging.log_message(
|
msg = {
|
||||||
"Received Message",
|
"id": 0,
|
||||||
packet["raw"],
|
"ts": time.time(),
|
||||||
msg,
|
"ack": False,
|
||||||
fromcall=fromcall,
|
"from": fromcall,
|
||||||
msg_num=msg_id,
|
"to": packet["to"],
|
||||||
)
|
"raw": packet["raw"],
|
||||||
# let any threads do their thing, then ack
|
"message": message,
|
||||||
# send an ack last
|
"status": None,
|
||||||
ack = messaging.AckMessage(
|
"last_update": None,
|
||||||
self.config["aprsd"]["callsign"],
|
"reply": None,
|
||||||
fromcall,
|
}
|
||||||
msg_id=msg_id,
|
self.socketio.emit(
|
||||||
)
|
"new", msg,
|
||||||
ack.send()
|
namespace="/sendmsg",
|
||||||
|
)
|
||||||
packets.PacketList().add(packet)
|
|
||||||
stats.APRSDStats().msgs_rx_inc()
|
|
||||||
message = packet.get("message_text", None)
|
|
||||||
msg = {
|
|
||||||
"id": 0,
|
|
||||||
"ts": time.time(),
|
|
||||||
"ack": False,
|
|
||||||
"from": fromcall,
|
|
||||||
"to": packet["to"],
|
|
||||||
"raw": packet["raw"],
|
|
||||||
"message": message,
|
|
||||||
"status": None,
|
|
||||||
"last_update": None,
|
|
||||||
"reply": None,
|
|
||||||
}
|
|
||||||
self.socketio.emit(
|
|
||||||
"new", msg,
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WebChatFlask(flask_classful.FlaskView):
|
class WebChatFlask(flask_classful.FlaskView):
|
||||||
@ -312,10 +218,7 @@ class WebChatFlask(flask_classful.FlaskView):
|
|||||||
|
|
||||||
users = self.users
|
users = self.users
|
||||||
|
|
||||||
@auth.login_required
|
def _get_transport(self, stats):
|
||||||
def index(self):
|
|
||||||
stats = self._stats()
|
|
||||||
|
|
||||||
if self.config["aprs"].get("enabled", True):
|
if self.config["aprs"].get("enabled", True):
|
||||||
transport = "aprs-is"
|
transport = "aprs-is"
|
||||||
aprs_connection = (
|
aprs_connection = (
|
||||||
@ -341,12 +244,35 @@ class WebChatFlask(flask_classful.FlaskView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return transport, aprs_connection
|
||||||
|
|
||||||
|
@auth.login_required
|
||||||
|
def index(self):
|
||||||
|
user_agent = request.headers.get("User-Agent")
|
||||||
|
device = DeviceDetector(user_agent).parse()
|
||||||
|
LOG.debug(f"Device type {device.device_type()}")
|
||||||
|
LOG.debug(f"Is mobile? {device.is_mobile()}")
|
||||||
|
stats = self._stats()
|
||||||
|
|
||||||
|
if device.is_mobile():
|
||||||
|
html_template = "mobile.html"
|
||||||
|
else:
|
||||||
|
html_template = "index.html"
|
||||||
|
|
||||||
|
# For development
|
||||||
|
# html_template = "mobile.html"
|
||||||
|
|
||||||
|
LOG.debug(f"Template {html_template}")
|
||||||
|
|
||||||
|
transport, aprs_connection = self._get_transport(stats)
|
||||||
|
LOG.debug(f"transport {transport} aprs_connection {aprs_connection}")
|
||||||
|
|
||||||
stats["transport"] = transport
|
stats["transport"] = transport
|
||||||
stats["aprs_connection"] = aprs_connection
|
stats["aprs_connection"] = aprs_connection
|
||||||
LOG.debug(f"initial stats = {stats}")
|
LOG.debug(f"initial stats = {stats}")
|
||||||
|
|
||||||
return flask.render_template(
|
return flask.render_template(
|
||||||
"index.html",
|
html_template,
|
||||||
initial_stats=stats,
|
initial_stats=stats,
|
||||||
aprs_connection=aprs_connection,
|
aprs_connection=aprs_connection,
|
||||||
callsign=self.config["aprsd"]["callsign"],
|
callsign=self.config["aprsd"]["callsign"],
|
||||||
@ -392,9 +318,8 @@ class SendMessageNamespace(Namespace):
|
|||||||
msg = None
|
msg = None
|
||||||
request = None
|
request = None
|
||||||
|
|
||||||
def __init__(self, namespace=None, config=None, msg_queues=None):
|
def __init__(self, namespace=None, config=None):
|
||||||
self._config = config
|
self._config = config
|
||||||
self._msg_queues = msg_queues
|
|
||||||
super().__init__(namespace)
|
super().__init__(namespace)
|
||||||
|
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
@ -404,13 +329,9 @@ class SendMessageNamespace(Namespace):
|
|||||||
"connected", {"data": "/sendmsg Connected"},
|
"connected", {"data": "/sendmsg Connected"},
|
||||||
namespace="/sendmsg",
|
namespace="/sendmsg",
|
||||||
)
|
)
|
||||||
msg = {"connected": True}
|
|
||||||
self._msg_queues["control"].put(msg)
|
|
||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
LOG.debug("WS Disconnected")
|
LOG.debug("WS Disconnected")
|
||||||
msg = {"connected": False}
|
|
||||||
self._msg_queues["control"].put(msg)
|
|
||||||
|
|
||||||
def on_send(self, data):
|
def on_send(self, data):
|
||||||
global socketio
|
global socketio
|
||||||
@ -419,7 +340,7 @@ class SendMessageNamespace(Namespace):
|
|||||||
data["from"] = self._config["aprs"]["login"]
|
data["from"] = self._config["aprs"]["login"]
|
||||||
msg = messaging.TextMessage(
|
msg = messaging.TextMessage(
|
||||||
data["from"],
|
data["from"],
|
||||||
data["to"],
|
data["to"].upper(),
|
||||||
data["message"],
|
data["message"],
|
||||||
)
|
)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
@ -432,7 +353,6 @@ class SendMessageNamespace(Namespace):
|
|||||||
namespace="/sendmsg",
|
namespace="/sendmsg",
|
||||||
)
|
)
|
||||||
msg.send()
|
msg.send()
|
||||||
# self._msg_queues["tx"].put(msg)
|
|
||||||
|
|
||||||
def on_gps(self, data):
|
def on_gps(self, data):
|
||||||
LOG.debug(f"WS on_GPS: {data}")
|
LOG.debug(f"WS on_GPS: {data}")
|
||||||
@ -541,7 +461,6 @@ def init_flask(config, loglevel, quiet):
|
|||||||
socketio.on_namespace(
|
socketio.on_namespace(
|
||||||
SendMessageNamespace(
|
SendMessageNamespace(
|
||||||
"/sendmsg", config=config,
|
"/sendmsg", config=config,
|
||||||
msg_queues=msg_queues,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return socketio, flask_app
|
return socketio, flask_app
|
||||||
@ -618,18 +537,11 @@ def webchat(ctx, flush, port):
|
|||||||
|
|
||||||
(socketio, app) = init_flask(config, loglevel, quiet)
|
(socketio, app) = init_flask(config, loglevel, quiet)
|
||||||
rx_thread = WebChatRXThread(
|
rx_thread = WebChatRXThread(
|
||||||
msg_queues=msg_queues,
|
|
||||||
config=config,
|
|
||||||
)
|
|
||||||
LOG.info("Start RX Thread")
|
|
||||||
rx_thread.start()
|
|
||||||
tx_thread = WebChatTXThread(
|
|
||||||
msg_queues=msg_queues,
|
|
||||||
config=config,
|
config=config,
|
||||||
socketio=socketio,
|
socketio=socketio,
|
||||||
)
|
)
|
||||||
LOG.info("Start TX Thread")
|
LOG.info("Start RX Thread")
|
||||||
tx_thread.start()
|
rx_thread.start()
|
||||||
|
|
||||||
keepalive = threads.KeepAliveThread(config=config)
|
keepalive = threads.KeepAliveThread(config=config)
|
||||||
LOG.info("Start KeepAliveThread")
|
LOG.info("Start KeepAliveThread")
|
||||||
|
@ -58,17 +58,32 @@ class APRSDRXThread(APRSDThread):
|
|||||||
|
|
||||||
|
|
||||||
class APRSDPluginRXThread(APRSDRXThread):
|
class APRSDPluginRXThread(APRSDRXThread):
|
||||||
|
"""Process received packets.
|
||||||
|
|
||||||
|
This is the main APRSD Server command thread that
|
||||||
|
receives packets from APRIS and then sends them for
|
||||||
|
processing in the PluginProcessPacketThread.
|
||||||
|
"""
|
||||||
def process_packet(self, *args, **kwargs):
|
def process_packet(self, *args, **kwargs):
|
||||||
packet = self._client.decode_packet(*args, **kwargs)
|
packet = self._client.decode_packet(*args, **kwargs)
|
||||||
thread = APRSDProcessPacketThread(packet=packet, config=self.config)
|
thread = APRSDPluginProcessPacketThread(
|
||||||
|
config=self.config,
|
||||||
|
packet=packet,
|
||||||
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
class APRSDProcessPacketThread(APRSDThread):
|
class APRSDProcessPacketThread(APRSDThread):
|
||||||
|
"""Base class for processing received packets.
|
||||||
|
|
||||||
def __init__(self, packet, config):
|
This is the base class for processing packets coming from
|
||||||
self.packet = packet
|
the consumer. This base class handles sending ack packets and
|
||||||
|
will ack a message before sending the packet to the subclass
|
||||||
|
for processing."""
|
||||||
|
|
||||||
|
def __init__(self, config, packet):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.packet = packet
|
||||||
name = self.packet["raw"][:10]
|
name = self.packet["raw"][:10]
|
||||||
super().__init__(f"RXPKT-{name}")
|
super().__init__(f"RXPKT-{name}")
|
||||||
|
|
||||||
@ -88,7 +103,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
"""Process a packet recieved from aprs-is server."""
|
"""Process a packet received from aprs-is server."""
|
||||||
packet = self.packet
|
packet = self.packet
|
||||||
packets.PacketList().add(packet)
|
packets.PacketList().add(packet)
|
||||||
|
|
||||||
@ -101,7 +116,11 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
|
|
||||||
# We don't put ack packets destined for us through the
|
# We don't put ack packets destined for us through the
|
||||||
# plugins.
|
# plugins.
|
||||||
if tocall == self.config["aprsd"]["callsign"] and msg_response == "ack":
|
if (
|
||||||
|
tocall
|
||||||
|
and tocall.lower() == self.config["aprsd"]["callsign"].lower()
|
||||||
|
and msg_response == "ack"
|
||||||
|
):
|
||||||
self.process_ack_packet(packet)
|
self.process_ack_packet(packet)
|
||||||
else:
|
else:
|
||||||
# It's not an ACK for us, so lets run it through
|
# It's not an ACK for us, so lets run it through
|
||||||
@ -115,7 +134,10 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Only ack messages that were sent directly to us
|
# Only ack messages that were sent directly to us
|
||||||
if (tocall.lower() == self.config["aprsd"]["callsign"].lower()):
|
if (
|
||||||
|
tocall
|
||||||
|
and tocall.lower() == self.config["aprsd"]["callsign"].lower()
|
||||||
|
):
|
||||||
stats.APRSDStats().msgs_rx_inc()
|
stats.APRSDStats().msgs_rx_inc()
|
||||||
# let any threads do their thing, then ack
|
# let any threads do their thing, then ack
|
||||||
# send an ack last
|
# send an ack last
|
||||||
@ -126,69 +148,89 @@ class APRSDProcessPacketThread(APRSDThread):
|
|||||||
)
|
)
|
||||||
ack.send()
|
ack.send()
|
||||||
|
|
||||||
pm = plugin.PluginManager()
|
self.process_non_ack_packet(packet)
|
||||||
try:
|
else:
|
||||||
results = pm.run(packet)
|
LOG.info("Packet was not for us.")
|
||||||
wl = packets.WatchList()
|
LOG.debug("Packet processing complete")
|
||||||
wl.update_seen(packet)
|
|
||||||
replied = False
|
|
||||||
for reply in results:
|
|
||||||
if isinstance(reply, list):
|
|
||||||
# one of the plugins wants to send multiple messages
|
|
||||||
replied = True
|
|
||||||
for subreply in reply:
|
|
||||||
LOG.debug(f"Sending '{subreply}'")
|
|
||||||
if isinstance(subreply, messaging.Message):
|
|
||||||
subreply.send()
|
|
||||||
else:
|
|
||||||
msg = messaging.TextMessage(
|
|
||||||
self.config["aprsd"]["callsign"],
|
|
||||||
fromcall,
|
|
||||||
subreply,
|
|
||||||
)
|
|
||||||
msg.send()
|
|
||||||
elif isinstance(reply, messaging.Message):
|
|
||||||
# We have a message based object.
|
|
||||||
LOG.debug(f"Sending '{reply}'")
|
|
||||||
reply.send()
|
|
||||||
replied = True
|
|
||||||
else:
|
|
||||||
replied = True
|
|
||||||
# A plugin can return a null message flag which signals
|
|
||||||
# us that they processed the message correctly, but have
|
|
||||||
# nothing to reply with, so we avoid replying with a
|
|
||||||
# usage string
|
|
||||||
if reply is not messaging.NULL_MESSAGE:
|
|
||||||
LOG.debug(f"Sending '{reply}'")
|
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def process_non_ack_packet(self, *args, **kwargs):
|
||||||
|
"""Ack packets already dealt with here."""
|
||||||
|
|
||||||
|
|
||||||
|
class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
||||||
|
"""Process the packet through the plugin manager.
|
||||||
|
|
||||||
|
This is the main aprsd server plugin processing thread."""
|
||||||
|
|
||||||
|
def process_non_ack_packet(self, packet):
|
||||||
|
"""Send the packet through the plugins."""
|
||||||
|
fromcall = packet["from"]
|
||||||
|
tocall = packet.get("addresse", None)
|
||||||
|
msg = packet.get("message_text", None)
|
||||||
|
packet.get("msgNo", "0")
|
||||||
|
packet.get("response", None)
|
||||||
|
pm = plugin.PluginManager()
|
||||||
|
try:
|
||||||
|
results = pm.run(packet)
|
||||||
|
wl = packets.WatchList()
|
||||||
|
wl.update_seen(packet)
|
||||||
|
replied = False
|
||||||
|
for reply in results:
|
||||||
|
if isinstance(reply, list):
|
||||||
|
# one of the plugins wants to send multiple messages
|
||||||
|
replied = True
|
||||||
|
for subreply in reply:
|
||||||
|
LOG.debug(f"Sending '{subreply}'")
|
||||||
|
if isinstance(subreply, messaging.Message):
|
||||||
|
subreply.send()
|
||||||
|
else:
|
||||||
msg = messaging.TextMessage(
|
msg = messaging.TextMessage(
|
||||||
self.config["aprsd"]["callsign"],
|
self.config["aprsd"]["callsign"],
|
||||||
fromcall,
|
fromcall,
|
||||||
reply,
|
subreply,
|
||||||
)
|
)
|
||||||
msg.send()
|
msg.send()
|
||||||
|
elif isinstance(reply, messaging.Message):
|
||||||
|
# We have a message based object.
|
||||||
|
LOG.debug(f"Sending '{reply}'")
|
||||||
|
reply.send()
|
||||||
|
replied = True
|
||||||
|
else:
|
||||||
|
replied = True
|
||||||
|
# A plugin can return a null message flag which signals
|
||||||
|
# us that they processed the message correctly, but have
|
||||||
|
# nothing to reply with, so we avoid replying with a
|
||||||
|
# usage string
|
||||||
|
if reply is not messaging.NULL_MESSAGE:
|
||||||
|
LOG.debug(f"Sending '{reply}'")
|
||||||
|
|
||||||
# If the message was for us and we didn't have a
|
msg = messaging.TextMessage(
|
||||||
# response, then we send a usage statement.
|
self.config["aprsd"]["callsign"],
|
||||||
if tocall == self.config["aprsd"]["callsign"] and not replied:
|
fromcall,
|
||||||
LOG.warning("Sending help!")
|
reply,
|
||||||
msg = messaging.TextMessage(
|
)
|
||||||
self.config["aprsd"]["callsign"],
|
msg.send()
|
||||||
fromcall,
|
|
||||||
"Unknown command! Send 'help' message for help",
|
|
||||||
)
|
|
||||||
msg.send()
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error("Plugin failed!!!")
|
|
||||||
LOG.exception(ex)
|
|
||||||
# Do we need to send a reply?
|
|
||||||
if tocall == self.config["aprsd"]["callsign"]:
|
|
||||||
reply = "A Plugin failed! try again?"
|
|
||||||
msg = messaging.TextMessage(
|
|
||||||
self.config["aprsd"]["callsign"],
|
|
||||||
fromcall,
|
|
||||||
reply,
|
|
||||||
)
|
|
||||||
msg.send()
|
|
||||||
|
|
||||||
LOG.debug("Packet processing complete")
|
# If the message was for us and we didn't have a
|
||||||
|
# response, then we send a usage statement.
|
||||||
|
if tocall == self.config["aprsd"]["callsign"] and not replied:
|
||||||
|
LOG.warning("Sending help!")
|
||||||
|
msg = messaging.TextMessage(
|
||||||
|
self.config["aprsd"]["callsign"],
|
||||||
|
fromcall,
|
||||||
|
"Unknown command! Send 'help' message for help",
|
||||||
|
)
|
||||||
|
msg.send()
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error("Plugin failed!!!")
|
||||||
|
LOG.exception(ex)
|
||||||
|
# Do we need to send a reply?
|
||||||
|
if tocall == self.config["aprsd"]["callsign"]:
|
||||||
|
reply = "A Plugin failed! try again?"
|
||||||
|
msg = messaging.TextMessage(
|
||||||
|
self.config["aprsd"]["callsign"],
|
||||||
|
fromcall,
|
||||||
|
reply,
|
||||||
|
)
|
||||||
|
msg.send()
|
||||||
|
63
aprsd/web/chat/static/js/gps.js
Normal file
63
aprsd/web/chat/static/js/gps.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
function init_gps() {
|
||||||
|
console.log("init_gps Called.")
|
||||||
|
$("#send_beacon").click(function() {
|
||||||
|
console.log("Send a beacon!")
|
||||||
|
getLocation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocation() {
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
console.log("getCurrentPosition");
|
||||||
|
navigator.geolocation.getCurrentPosition(showPosition, showError);
|
||||||
|
} else {
|
||||||
|
var msg = "Geolocation is not supported by this browser."
|
||||||
|
console.log(msg);
|
||||||
|
alert(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(error) {
|
||||||
|
console.log("showError");
|
||||||
|
console.log(error);
|
||||||
|
var msg = "";
|
||||||
|
switch(error.code) {
|
||||||
|
case error.PERMISSION_DENIED:
|
||||||
|
msg = "User denied the request for Geolocation."
|
||||||
|
break;
|
||||||
|
case error.POSITION_UNAVAILABLE:
|
||||||
|
msg = "Location information is unavailable."
|
||||||
|
break;
|
||||||
|
case error.TIMEOUT:
|
||||||
|
msg = "The request to get user location timed out."
|
||||||
|
break;
|
||||||
|
case error.UNKNOWN_ERROR:
|
||||||
|
msg = "An unknown error occurred."
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.log(msg);
|
||||||
|
$.toast({
|
||||||
|
title: 'GPS Error',
|
||||||
|
message: msg,
|
||||||
|
showProgress: 'bottom',
|
||||||
|
classProgress: 'red'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPosition(position) {
|
||||||
|
console.log("showPosition Called");
|
||||||
|
msg = {
|
||||||
|
'latitude': position.coords.latitude,
|
||||||
|
'longitude': position.coords.longitude
|
||||||
|
}
|
||||||
|
console.log(msg);
|
||||||
|
$.toast({
|
||||||
|
title: 'Sending GPS Beacon',
|
||||||
|
message: "Latitude: "+position.coords.latitude+"<br>Longitude: "+position.coords.longitude,
|
||||||
|
showProgress: 'bottom',
|
||||||
|
classProgress: 'red'
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit("gps", msg);
|
||||||
|
}
|
223
aprsd/web/chat/static/js/send-message-mobile.js
Normal file
223
aprsd/web/chat/static/js/send-message-mobile.js
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
var cleared = false;
|
||||||
|
var callsign_list = {};
|
||||||
|
var message_list = {};
|
||||||
|
|
||||||
|
function size_dict(d){c=0; for (i in d) ++c; return c}
|
||||||
|
|
||||||
|
function init_chat() {
|
||||||
|
const socket = io("/sendmsg");
|
||||||
|
socket.on('connect', function () {
|
||||||
|
console.log("Connected to socketio");
|
||||||
|
});
|
||||||
|
socket.on('connected', function(msg) {
|
||||||
|
console.log("Connected!");
|
||||||
|
console.log(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("sent", function(msg) {
|
||||||
|
if (cleared == false) {
|
||||||
|
var msgsdiv = $("#msgsTabsDiv");
|
||||||
|
msgsdiv.html('')
|
||||||
|
cleared = true
|
||||||
|
}
|
||||||
|
sent_msg(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("ack", function(msg) {
|
||||||
|
update_msg(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("new", function(msg) {
|
||||||
|
if (cleared == false) {
|
||||||
|
var msgsdiv = $("#msgsTabsDiv");
|
||||||
|
msgsdiv.html('')
|
||||||
|
cleared = true
|
||||||
|
}
|
||||||
|
from_msg(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#sendform").submit(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
msg = {'to': $('#to_call').val().toUpperCase(),
|
||||||
|
'message': $('#message').val(),
|
||||||
|
}
|
||||||
|
socket.emit("send", msg);
|
||||||
|
$('#message').val('');
|
||||||
|
$('#to_call').val('');
|
||||||
|
});
|
||||||
|
|
||||||
|
init_gps();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function add_callsign(callsign) {
|
||||||
|
/* Ensure a callsign exists in the left hand nav */
|
||||||
|
dropdown = $('#callsign_dropdown')
|
||||||
|
|
||||||
|
if (callsign in callsign_list) {
|
||||||
|
console.log(callsign+' already in list.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var callsignTabs = $("#callsignTabs");
|
||||||
|
tab_name = tab_string(callsign);
|
||||||
|
tab_content = tab_content_name(callsign);
|
||||||
|
divname = content_divname(callsign);
|
||||||
|
|
||||||
|
item_html = '<div class="active item" id="'+tab_name+'" onclick="openCallsign(event, \''+callsign+'\');">'+callsign+'</div>';
|
||||||
|
callsignTabs.append(item_html);
|
||||||
|
|
||||||
|
callsign_list[callsign] = {'name': callsign, 'value': callsign, 'text': callsign}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function append_message(callsign, msg, msg_html) {
|
||||||
|
console.log('append_message');
|
||||||
|
new_callsign = false
|
||||||
|
if (!message_list.hasOwnProperty(callsign)) {
|
||||||
|
message_list[callsign] = new Array();
|
||||||
|
}
|
||||||
|
message_list[callsign].push(msg);
|
||||||
|
|
||||||
|
// Find the right div to place the html
|
||||||
|
new_callsign = add_callsign(callsign);
|
||||||
|
append_message_html(callsign, msg_html, new_callsign);
|
||||||
|
if (new_callsign) {
|
||||||
|
//click on the new tab
|
||||||
|
click_div = '#'+tab_string(callsign);
|
||||||
|
console.log("Click on "+click_div);
|
||||||
|
$(click_div).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tab_string(callsign) {
|
||||||
|
return "msgs"+callsign;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tab_content_name(callsign) {
|
||||||
|
return tab_string(callsign)+"Content";
|
||||||
|
}
|
||||||
|
|
||||||
|
function content_divname(callsign) {
|
||||||
|
return "#"+tab_content_name(callsign);
|
||||||
|
}
|
||||||
|
|
||||||
|
function append_message_html(callsign, msg_html, new_callsign) {
|
||||||
|
var msgsTabs = $('#msgsTabsDiv');
|
||||||
|
divname_str = tab_content_name(callsign);
|
||||||
|
divname = content_divname(callsign);
|
||||||
|
if (new_callsign) {
|
||||||
|
// we have to add a new DIV
|
||||||
|
msg_div_html = '<div class="tabcontent" id="'+divname_str+'" style="height:450px;">'+msg_html+'</div>';
|
||||||
|
msgsTabs.append(msg_div_html);
|
||||||
|
} else {
|
||||||
|
var msgDiv = $(divname);
|
||||||
|
msgDiv.append(msg_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(divname).animate({scrollTop: $(divname)[0].scrollHeight}, "slow");
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_message_html(time, from, to, message, ack) {
|
||||||
|
msg_html = '<div class="item">';
|
||||||
|
msg_html += '<div class="tiny text">'+time+'</div>';
|
||||||
|
msg_html += '<div class="middle aligned content">';
|
||||||
|
msg_html += '<div class="tiny red header">'+from+'</div>';
|
||||||
|
if (ack) {
|
||||||
|
msg_html += '<i class="thumbs down outline icon" id="' + ack_id + '" data-content="Waiting for ACK"></i>';
|
||||||
|
} else {
|
||||||
|
msg_html += '<i class="phone volume icon" data-content="Recieved Message"></i>';
|
||||||
|
}
|
||||||
|
msg_html += '<div class="middle aligned content">> </div>';
|
||||||
|
msg_html += '</div>';
|
||||||
|
msg_html += '<div class="middle aligned content">'+message+'</div>';
|
||||||
|
msg_html += '</div><br>';
|
||||||
|
|
||||||
|
return msg_html
|
||||||
|
}
|
||||||
|
|
||||||
|
function sent_msg(msg) {
|
||||||
|
var msgsdiv = $("#sendMsgsDiv");
|
||||||
|
|
||||||
|
ts_str = msg["ts"].toString();
|
||||||
|
ts = ts_str.split(".")[0]*1000;
|
||||||
|
id = ts_str.split('.')[0]
|
||||||
|
ack_id = "ack_" + id
|
||||||
|
|
||||||
|
var d = new Date(ts).toLocaleDateString("en-US")
|
||||||
|
var t = new Date(ts).toLocaleTimeString("en-US")
|
||||||
|
|
||||||
|
msg_html = create_message_html(t, msg['from'], msg['to'], msg['message'], ack_id);
|
||||||
|
append_message(msg['to'], msg, msg_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function from_msg(msg) {
|
||||||
|
var msgsdiv = $("#sendMsgsDiv");
|
||||||
|
|
||||||
|
// We have an existing entry
|
||||||
|
ts_str = msg["ts"].toString();
|
||||||
|
ts = ts_str.split(".")[0]*1000;
|
||||||
|
id = ts_str.split('.')[0]
|
||||||
|
ack_id = "ack_" + id
|
||||||
|
|
||||||
|
var d = new Date(ts).toLocaleDateString("en-US")
|
||||||
|
var t = new Date(ts).toLocaleTimeString("en-US")
|
||||||
|
|
||||||
|
from = msg['from']
|
||||||
|
msg_html = create_message_html(t, from, false, msg['message'], false);
|
||||||
|
append_message(from, msg, msg_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_msg(msg) {
|
||||||
|
var msgsdiv = $("#sendMsgsDiv");
|
||||||
|
// We have an existing entry
|
||||||
|
ts_str = msg["ts"].toString();
|
||||||
|
id = ts_str.split('.')[0]
|
||||||
|
pretty_id = "pretty_" + id
|
||||||
|
loader_id = "loader_" + id
|
||||||
|
ack_id = "ack_" + id
|
||||||
|
span_id = "span_" + id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (msg['ack'] == true) {
|
||||||
|
var loader_div = $('#' + loader_id);
|
||||||
|
var ack_div = $('#' + ack_id);
|
||||||
|
loader_div.removeClass('ui active inline loader');
|
||||||
|
loader_div.addClass('ui disabled loader');
|
||||||
|
ack_div.removeClass('thumbs up outline icon');
|
||||||
|
ack_div.addClass('thumbs up outline icon');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.ui.accordion').accordion('refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
function callsign_select(callsign) {
|
||||||
|
var tocall = $("#to_call");
|
||||||
|
tocall.val(callsign);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset_Tabs() {
|
||||||
|
tabcontent = document.getElementsByClassName("tabcontent");
|
||||||
|
for (i = 0; i < tabcontent.length; i++) {
|
||||||
|
tabcontent[i].style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCallsign(evt, callsign) {
|
||||||
|
var i, tabcontent, tablinks;
|
||||||
|
|
||||||
|
tab_content = tab_content_name(callsign);
|
||||||
|
|
||||||
|
tabcontent = document.getElementsByClassName("tabcontent");
|
||||||
|
for (i = 0; i < tabcontent.length; i++) {
|
||||||
|
tabcontent[i].style.display = "none";
|
||||||
|
}
|
||||||
|
tablinks = document.getElementsByClassName("tablinks");
|
||||||
|
for (i = 0; i < tablinks.length; i++) {
|
||||||
|
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||||
|
}
|
||||||
|
document.getElementById(tab_content).style.display = "block";
|
||||||
|
evt.target.className += " active";
|
||||||
|
callsign_select(callsign);
|
||||||
|
}
|
@ -45,56 +45,9 @@ function init_chat() {
|
|||||||
$('#message').val('');
|
$('#message').val('');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#send_beacon").click(function() {
|
init_gps();
|
||||||
console.log("Send a beacon!")
|
|
||||||
getLocation();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLocation() {
|
|
||||||
if (navigator.geolocation) {
|
|
||||||
console.log("getCurrentPosition");
|
|
||||||
navigator.geolocation.getCurrentPosition(showPosition, showError);
|
|
||||||
} else {
|
|
||||||
var msg = "Geolocation is not supported by this browser."
|
|
||||||
console.log(msg);
|
|
||||||
alert(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(error) {
|
|
||||||
console.log("showError");
|
|
||||||
console.log(error);
|
|
||||||
var msg = "";
|
|
||||||
switch(error.code) {
|
|
||||||
case error.PERMISSION_DENIED:
|
|
||||||
msg = "User denied the request for Geolocation."
|
|
||||||
break;
|
|
||||||
case error.POSITION_UNAVAILABLE:
|
|
||||||
msg = "Location information is unavailable."
|
|
||||||
break;
|
|
||||||
case error.TIMEOUT:
|
|
||||||
msg = "The request to get user location timed out."
|
|
||||||
break;
|
|
||||||
case error.UNKNOWN_ERROR:
|
|
||||||
msg = "An unknown error occurred."
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log(msg);
|
|
||||||
alert(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPosition(position) {
|
|
||||||
console.log("showPosition Called");
|
|
||||||
msg = {
|
|
||||||
'latitude': position.coords.latitude,
|
|
||||||
'longitude': position.coords.longitude
|
|
||||||
}
|
|
||||||
console.log(msg);
|
|
||||||
socket.emit("gps", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function add_callsign(callsign) {
|
function add_callsign(callsign) {
|
||||||
/* Ensure a callsign exists in the left hand nav */
|
/* Ensure a callsign exists in the left hand nav */
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
||||||
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
|
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/index.css">
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
<link rel="stylesheet" href="/static/css/tabs.css">
|
<link rel="stylesheet" href="/static/css/tabs.css">
|
||||||
<script src="/static/js/main.js"></script>
|
<script src="/static/js/main.js"></script>
|
||||||
|
<script src="/static/js/gps.js"></script>
|
||||||
<script src="/static/js/send-message.js"></script>
|
<script src="/static/js/send-message.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var initial_stats = {{ initial_stats|tojson|safe }};
|
var initial_stats = {{ initial_stats|tojson|safe }};
|
||||||
|
|
||||||
@ -66,16 +66,14 @@
|
|||||||
<button type="button" class="ui button" id="send_beacon" value="Send GPS Beacon">Send GPS Beacon</button>
|
<button type="button" class="ui button" id="send_beacon" value="Send GPS Beacon">Send GPS Beacon</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="three wide column">
|
<div class="three wide column">
|
||||||
<div class="tab" id="callsignTabs">
|
<div class="tab" id="callsignTabs"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="ten wide column ui raised segment" id="msgsTabsDiv" style="height:450px;padding:0px;">
|
||||||
<div class="ten wide column ui raised segment" id="msgsTabsDiv" style="height:450px;padding:0px;">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
82
aprsd/web/chat/templates/mobile.html
Normal file
82
aprsd/web/chat/templates/mobile.html
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
||||||
|
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
|
<script src="/static/js/main.js"></script>
|
||||||
|
<script src="/static/js/gps.js"></script>
|
||||||
|
<script src="/static/js/send-message-mobile.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var initial_stats = {{ initial_stats|tojson|safe }};
|
||||||
|
|
||||||
|
var memory_chart = null
|
||||||
|
var message_chart = null
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
console.log(initial_stats);
|
||||||
|
start_update();
|
||||||
|
init_chat();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class='ui text container'>
|
||||||
|
<h1 class='ui dividing header'>APRSD WebChat {{ version }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='ui grid text container' style="padding-bottom: 5px;">
|
||||||
|
<div class='left floated twelve wide column'>
|
||||||
|
<span style='color: green'>{{ callsign }}</span>
|
||||||
|
connected to
|
||||||
|
<span style='color: blue' id='aprs_connection'>{{ aprs_connection|safe }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='right floated four wide column'>
|
||||||
|
<span id='uptime'>NONE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sendMsgDiv" class="ui grid" align="left" style="padding-top: 2px;">
|
||||||
|
<h3 class="sixteen wide column ui dividing header">Send Message</h3>
|
||||||
|
<form id="sendform" name="sendmsg" action="">
|
||||||
|
<div class="sixteen wide column ui left labeled icon input">
|
||||||
|
<div class="ui label">Callsign</div>
|
||||||
|
<input type="text" name="to_call" id="to_call" placeholder="To Callsign" size="11" maxlength="9">
|
||||||
|
<i class="users icon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="sixteen wide column ui left labeled icon input" style="padding-bottom: 5px;">
|
||||||
|
<label for="message" class="ui label">Message</label>
|
||||||
|
<input type="text" name="message" id="message" maxlength="40" placeholder="Message">
|
||||||
|
<i class="comment icon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="right floated column">
|
||||||
|
<input type="submit" name="submit" class="ui button" id="send_msg" value="Send" />
|
||||||
|
<button type="button" class="ui button" id="send_beacon" value="Send GPS Beacon">Send GPS Beacon</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="ui top attached tabular raised menu" id="callsignTabs">
|
||||||
|
</div>
|
||||||
|
<div class="sixteen wide column ui bottom attached raised tab segment" id="msgsTabsDiv" style="height:250px;padding:5px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui text container" style="padding-top: 40px">
|
||||||
|
<a href="https://badge.fury.io/py/aprsd"><img src="https://badge.fury.io/py/aprsd.svg" alt="PyPI version" height="18"></a>
|
||||||
|
<a href="https://github.com/craigerl/aprsd"><img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" height="18"></a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -7,13 +7,12 @@
|
|||||||
add-trailing-comma==2.3.0 # via gray
|
add-trailing-comma==2.3.0 # via gray
|
||||||
alabaster==0.7.12 # via sphinx
|
alabaster==0.7.12 # via sphinx
|
||||||
attrs==22.1.0 # via jsonschema, pytest
|
attrs==22.1.0 # via jsonschema, pytest
|
||||||
autoflake==1.7.7 # via gray
|
autoflake==2.0.0 # via gray
|
||||||
babel==2.11.0 # via sphinx
|
babel==2.11.0 # via sphinx
|
||||||
black==22.10.0 # via gray
|
black==22.10.0 # via gray
|
||||||
bleach==5.0.1 # via readme-renderer
|
bleach==5.0.1 # via readme-renderer
|
||||||
build==0.9.0 # via pip-tools
|
build==0.9.0 # via pip-tools
|
||||||
certifi==2022.9.24 # via requests
|
certifi==2022.9.24 # via requests
|
||||||
cffi==1.15.1 # via cryptography
|
|
||||||
cfgv==3.3.1 # via pre-commit
|
cfgv==3.3.1 # via pre-commit
|
||||||
charset-normalizer==2.1.1 # via requests
|
charset-normalizer==2.1.1 # via requests
|
||||||
click==8.1.3 # via black, pip-tools
|
click==8.1.3 # via black, pip-tools
|
||||||
@ -21,7 +20,6 @@ colorlog==6.7.0 # via prettylog
|
|||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
configargparse==1.5.3 # via gray
|
configargparse==1.5.3 # via gray
|
||||||
coverage[toml]==6.5.0 # via pytest-cov
|
coverage[toml]==6.5.0 # via pytest-cov
|
||||||
cryptography==38.0.3 # via secretstorage
|
|
||||||
distlib==0.3.6 # via virtualenv
|
distlib==0.3.6 # via virtualenv
|
||||||
docutils==0.19 # via readme-renderer, sphinx
|
docutils==0.19 # via readme-renderer, sphinx
|
||||||
exceptiongroup==1.0.4 # via pytest
|
exceptiongroup==1.0.4 # via pytest
|
||||||
@ -38,9 +36,8 @@ importlib-resources==5.10.0 # via fixit, jsonschema
|
|||||||
iniconfig==1.1.1 # via pytest
|
iniconfig==1.1.1 # via pytest
|
||||||
isort==5.10.1 # via -r dev-requirements.in, gray
|
isort==5.10.1 # via -r dev-requirements.in, gray
|
||||||
jaraco-classes==3.2.3 # via keyring
|
jaraco-classes==3.2.3 # via keyring
|
||||||
jeepney==0.8.0 # via keyring, secretstorage
|
|
||||||
jinja2==3.1.2 # via sphinx
|
jinja2==3.1.2 # via sphinx
|
||||||
jsonschema==4.17.1 # via fixit
|
jsonschema==4.17.3 # via fixit
|
||||||
keyring==23.11.0 # via twine
|
keyring==23.11.0 # via twine
|
||||||
libcst==0.4.9 # via fixit
|
libcst==0.4.9 # via fixit
|
||||||
markupsafe==2.1.1 # via jinja2
|
markupsafe==2.1.1 # via jinja2
|
||||||
@ -54,7 +51,7 @@ pathspec==0.10.2 # via black
|
|||||||
pep517==0.13.0 # via build
|
pep517==0.13.0 # via build
|
||||||
pep8-naming==0.13.2 # via -r dev-requirements.in
|
pep8-naming==0.13.2 # via -r dev-requirements.in
|
||||||
pip-tools==6.10.0 # via -r dev-requirements.in
|
pip-tools==6.10.0 # via -r dev-requirements.in
|
||||||
pkginfo==1.8.3 # via twine
|
pkginfo==1.9.2 # via twine
|
||||||
pkgutil-resolve-name==1.3.10 # via jsonschema
|
pkgutil-resolve-name==1.3.10 # via jsonschema
|
||||||
platformdirs==2.5.4 # via black, virtualenv
|
platformdirs==2.5.4 # via black, virtualenv
|
||||||
pluggy==1.0.0 # via pytest, tox
|
pluggy==1.0.0 # via pytest, tox
|
||||||
@ -62,7 +59,6 @@ pre-commit==2.20.0 # via -r dev-requirements.in
|
|||||||
prettylog==0.3.0 # via gray
|
prettylog==0.3.0 # via gray
|
||||||
py==1.11.0 # via tox
|
py==1.11.0 # via tox
|
||||||
pycodestyle==2.10.0 # via flake8
|
pycodestyle==2.10.0 # via flake8
|
||||||
pycparser==2.21 # via cffi
|
|
||||||
pyflakes==3.0.1 # via autoflake, flake8
|
pyflakes==3.0.1 # via autoflake, flake8
|
||||||
pygments==2.13.0 # via readme-renderer, rich, sphinx
|
pygments==2.13.0 # via readme-renderer, rich, sphinx
|
||||||
pyparsing==3.0.9 # via packaging
|
pyparsing==3.0.9 # via packaging
|
||||||
@ -70,14 +66,13 @@ pyrsistent==0.19.2 # via jsonschema
|
|||||||
pytest==7.2.0 # via -r dev-requirements.in, pytest-cov
|
pytest==7.2.0 # via -r dev-requirements.in, pytest-cov
|
||||||
pytest-cov==4.0.0 # via -r dev-requirements.in
|
pytest-cov==4.0.0 # via -r dev-requirements.in
|
||||||
pytz==2022.6 # via babel
|
pytz==2022.6 # via babel
|
||||||
pyupgrade==3.2.2 # via gray
|
pyupgrade==3.2.3 # via gray
|
||||||
pyyaml==6.0 # via fixit, libcst, pre-commit
|
pyyaml==6.0 # via fixit, libcst, pre-commit
|
||||||
readme-renderer==37.3 # via twine
|
readme-renderer==37.3 # via twine
|
||||||
requests==2.28.1 # via requests-toolbelt, sphinx, twine
|
requests==2.28.1 # via requests-toolbelt, sphinx, twine
|
||||||
requests-toolbelt==0.10.1 # via twine
|
requests-toolbelt==0.10.1 # via twine
|
||||||
rfc3986==2.0.0 # via twine
|
rfc3986==2.0.0 # via twine
|
||||||
rich==12.6.0 # via twine
|
rich==12.6.0 # via twine
|
||||||
secretstorage==3.3.3 # via keyring
|
|
||||||
six==1.16.0 # via bleach, tox
|
six==1.16.0 # via bleach, tox
|
||||||
snowballstemmer==2.2.0 # via sphinx
|
snowballstemmer==2.2.0 # via sphinx
|
||||||
sphinx==5.3.0 # via -r dev-requirements.in
|
sphinx==5.3.0 # via -r dev-requirements.in
|
||||||
@ -98,10 +93,10 @@ ujson==5.5.0 # via fast-json
|
|||||||
unify==0.5 # via gray
|
unify==0.5 # via gray
|
||||||
untokenize==0.1.1 # via unify
|
untokenize==0.1.1 # via unify
|
||||||
urllib3==1.26.13 # via requests, twine
|
urllib3==1.26.13 # via requests, twine
|
||||||
virtualenv==20.16.7 # via pre-commit, tox
|
virtualenv==20.17.0 # via pre-commit, tox
|
||||||
webencodings==0.5.1 # via bleach
|
webencodings==0.5.1 # via bleach
|
||||||
wheel==0.38.4 # via pip-tools
|
wheel==0.38.4 # via pip-tools
|
||||||
zipp==3.10.0 # via importlib-metadata, importlib-resources
|
zipp==3.11.0 # via importlib-metadata, importlib-resources
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
# pip
|
# pip
|
||||||
|
@ -24,3 +24,5 @@ wrapt
|
|||||||
# kiss3 uses attrs
|
# kiss3 uses attrs
|
||||||
kiss3
|
kiss3
|
||||||
attrs==22.1.0
|
attrs==22.1.0
|
||||||
|
# for mobile checking
|
||||||
|
device-detector
|
||||||
|
@ -15,6 +15,7 @@ charset-normalizer==2.1.1 # via requests
|
|||||||
click==8.1.3 # via -r requirements.in, click-completion, flask
|
click==8.1.3 # via -r requirements.in, click-completion, flask
|
||||||
click-completion==0.5.2 # via -r requirements.in
|
click-completion==0.5.2 # via -r requirements.in
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
|
device-detector==5.0.1 # via -r requirements.in
|
||||||
dnspython==2.2.1 # via eventlet
|
dnspython==2.2.1 # via eventlet
|
||||||
eventlet==0.33.2 # via -r requirements.in
|
eventlet==0.33.2 # via -r requirements.in
|
||||||
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
||||||
@ -37,7 +38,8 @@ pyserial-asyncio==0.6 # via kiss3
|
|||||||
python-engineio==4.3.4 # via python-socketio
|
python-engineio==4.3.4 # via python-socketio
|
||||||
python-socketio==5.7.2 # via flask-socketio
|
python-socketio==5.7.2 # via flask-socketio
|
||||||
pytz==2022.6 # via -r requirements.in
|
pytz==2022.6 # via -r requirements.in
|
||||||
pyyaml==6.0 # via -r requirements.in
|
pyyaml==6.0 # via -r requirements.in, device-detector
|
||||||
|
regex==2022.10.31 # via device-detector
|
||||||
requests==2.28.1 # via -r requirements.in, update-checker
|
requests==2.28.1 # via -r requirements.in, update-checker
|
||||||
rich==12.6.0 # via -r requirements.in
|
rich==12.6.0 # via -r requirements.in
|
||||||
shellingham==1.5.0 # via click-completion
|
shellingham==1.5.0 # via click-completion
|
||||||
@ -50,4 +52,4 @@ update-checker==0.18.0 # via -r requirements.in
|
|||||||
urllib3==1.26.13 # via requests
|
urllib3==1.26.13 # via requests
|
||||||
werkzeug==2.1.2 # via -r requirements.in, flask
|
werkzeug==2.1.2 # via -r requirements.in, flask
|
||||||
wrapt==1.14.1 # via -r requirements.in
|
wrapt==1.14.1 # via -r requirements.in
|
||||||
zipp==3.10.0 # via importlib-metadata
|
zipp==3.11.0 # via importlib-metadata
|
||||||
|
Loading…
x
Reference in New Issue
Block a user