mirror of
https://github.com/craigerl/aprsd.git
synced 2025-07-10 04:45:16 -04:00
Remove webchat as a built in command.
Webchat will now be an extension that can be installed. the extension is here: https://github.com/hemna/aprsd-webchat-extension Install it from git or pypi.
This commit is contained in:
parent
c48ff8dfd4
commit
8f8887f0e4
aprsd
cmds
conf
log
main.pyweb
__init__.py
admin
__init__.py
static
css
images
Untitled.pngaprs-symbols-16-0.pngaprs-symbols-16-1.pngaprs-symbols-64-0.pngaprs-symbols-64-1.pngaprs-symbols-64-2.png
js
templates
chat
tests/cmds
@ -1,643 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
import click
|
|
||||||
import flask
|
|
||||||
from flask import request
|
|
||||||
from flask_httpauth import HTTPBasicAuth
|
|
||||||
from flask_socketio import Namespace, SocketIO
|
|
||||||
from geopy.distance import geodesic
|
|
||||||
from oslo_config import cfg
|
|
||||||
import timeago
|
|
||||||
import wrapt
|
|
||||||
|
|
||||||
import aprsd
|
|
||||||
from aprsd import cli_helper, client, packets, plugin_utils, stats, threads
|
|
||||||
from aprsd import utils
|
|
||||||
from aprsd import utils as aprsd_utils
|
|
||||||
from aprsd.client import client_factory, kiss
|
|
||||||
from aprsd.main import cli
|
|
||||||
from aprsd.threads import aprsd as aprsd_threads
|
|
||||||
from aprsd.threads import keep_alive, rx
|
|
||||||
from aprsd.threads import stats as stats_thread
|
|
||||||
from aprsd.threads import tx
|
|
||||||
from aprsd.utils import trace
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
LOG = logging.getLogger()
|
|
||||||
auth = HTTPBasicAuth()
|
|
||||||
socketio = None
|
|
||||||
|
|
||||||
# List of callsigns that we don't want to track/fetch their location
|
|
||||||
callsign_no_track = [
|
|
||||||
"APDW16", "BLN0", "BLN1", "BLN2",
|
|
||||||
"BLN3", "BLN4", "BLN5", "BLN6", "BLN7", "BLN8", "BLN9",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Callsign location information
|
|
||||||
# callsign: {lat: 0.0, long: 0.0, last_update: datetime}
|
|
||||||
callsign_locations = {}
|
|
||||||
|
|
||||||
flask_app = flask.Flask(
|
|
||||||
"aprsd",
|
|
||||||
static_url_path="/static",
|
|
||||||
static_folder="web/chat/static",
|
|
||||||
template_folder="web/chat/templates",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
|
||||||
|
|
||||||
click.echo("signal_handler: called")
|
|
||||||
LOG.info(
|
|
||||||
f"Ctrl+C, Sending all threads({len(threads.APRSDThreadList())}) exit! "
|
|
||||||
f"Can take up to 10 seconds {datetime.datetime.now()}",
|
|
||||||
)
|
|
||||||
threads.APRSDThreadList().stop_all()
|
|
||||||
if "subprocess" not in str(frame):
|
|
||||||
time.sleep(1.5)
|
|
||||||
stats.stats_collector.collect()
|
|
||||||
LOG.info("Telling flask to bail.")
|
|
||||||
signal.signal(signal.SIGTERM, sys.exit(0))
|
|
||||||
|
|
||||||
|
|
||||||
class SentMessages:
|
|
||||||
|
|
||||||
_instance = None
|
|
||||||
lock = threading.Lock()
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
"""This magic turns this into a singleton."""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def is_initialized(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def add(self, msg):
|
|
||||||
self.data[msg.msgNo] = msg.__dict__
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.data.keys())
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def get(self, id):
|
|
||||||
if id in self.data:
|
|
||||||
return self.data[id]
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def get_all(self):
|
|
||||||
return self.data
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def set_status(self, id, status):
|
|
||||||
if id in self.data:
|
|
||||||
self.data[id]["last_update"] = str(datetime.datetime.now())
|
|
||||||
self.data[id]["status"] = status
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def ack(self, id):
|
|
||||||
"""The message got an ack!"""
|
|
||||||
if id in self.data:
|
|
||||||
self.data[id]["last_update"] = str(datetime.datetime.now())
|
|
||||||
self.data[id]["ack"] = True
|
|
||||||
|
|
||||||
@wrapt.synchronized(lock)
|
|
||||||
def reply(self, id, packet):
|
|
||||||
"""We got a packet back from the sent message."""
|
|
||||||
if id in self.data:
|
|
||||||
self.data[id]["reply"] = packet
|
|
||||||
|
|
||||||
|
|
||||||
def _build_location_from_repeat(message):
|
|
||||||
# This is a location message Format is
|
|
||||||
# ^ld^callsign:latitude,longitude,altitude,course,speed,timestamp
|
|
||||||
a = message.split(":")
|
|
||||||
LOG.warning(a)
|
|
||||||
if len(a) == 2:
|
|
||||||
callsign = a[0].replace("^ld^", "")
|
|
||||||
b = a[1].split(",")
|
|
||||||
LOG.warning(b)
|
|
||||||
if len(b) == 6:
|
|
||||||
lat = float(b[0])
|
|
||||||
lon = float(b[1])
|
|
||||||
alt = float(b[2])
|
|
||||||
course = float(b[3])
|
|
||||||
speed = float(b[4])
|
|
||||||
time = int(b[5])
|
|
||||||
compass_bearing = aprsd_utils.degrees_to_cardinal(course)
|
|
||||||
data = {
|
|
||||||
"callsign": callsign,
|
|
||||||
"lat": lat,
|
|
||||||
"lon": lon,
|
|
||||||
"altitude": alt,
|
|
||||||
"course": course,
|
|
||||||
"compass_bearing": compass_bearing,
|
|
||||||
"speed": speed,
|
|
||||||
"lasttime": time,
|
|
||||||
"timeago": timeago.format(time),
|
|
||||||
}
|
|
||||||
LOG.debug(f"Location data from REPEAT {data}")
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _calculate_location_data(location_data):
|
|
||||||
"""Calculate all of the location data from data from aprs.fi or REPEAT."""
|
|
||||||
lat = location_data["lat"]
|
|
||||||
lon = location_data["lon"]
|
|
||||||
alt = location_data["altitude"]
|
|
||||||
speed = location_data["speed"]
|
|
||||||
lasttime = location_data["lasttime"]
|
|
||||||
timeago_str = location_data.get(
|
|
||||||
"timeago",
|
|
||||||
timeago.format(lasttime),
|
|
||||||
)
|
|
||||||
# now calculate distance from our own location
|
|
||||||
distance = 0
|
|
||||||
if CONF.webchat.latitude and CONF.webchat.longitude:
|
|
||||||
our_lat = float(CONF.webchat.latitude)
|
|
||||||
our_lon = float(CONF.webchat.longitude)
|
|
||||||
distance = geodesic((our_lat, our_lon), (lat, lon)).kilometers
|
|
||||||
bearing = aprsd_utils.calculate_initial_compass_bearing(
|
|
||||||
(our_lat, our_lon),
|
|
||||||
(lat, lon),
|
|
||||||
)
|
|
||||||
compass_bearing = aprsd_utils.degrees_to_cardinal(bearing)
|
|
||||||
return {
|
|
||||||
"callsign": location_data["callsign"],
|
|
||||||
"lat": lat,
|
|
||||||
"lon": lon,
|
|
||||||
"altitude": alt,
|
|
||||||
"course": f"{bearing:0.1f}",
|
|
||||||
"compass_bearing": compass_bearing,
|
|
||||||
"speed": speed,
|
|
||||||
"lasttime": lasttime,
|
|
||||||
"timeago": timeago_str,
|
|
||||||
"distance": f"{distance:0.1f}",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def send_location_data_to_browser(location_data):
|
|
||||||
global socketio
|
|
||||||
callsign = location_data["callsign"]
|
|
||||||
LOG.info(f"Got location for {callsign} {callsign_locations[callsign]}")
|
|
||||||
socketio.emit(
|
|
||||||
"callsign_location", callsign_locations[callsign],
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def populate_callsign_location(callsign, data=None):
|
|
||||||
"""Populate the location for the callsign.
|
|
||||||
|
|
||||||
if data is passed in, then we have the location already from
|
|
||||||
an APRS packet. If data is None, then we need to fetch the
|
|
||||||
location from aprs.fi or REPEAT.
|
|
||||||
"""
|
|
||||||
global socketio
|
|
||||||
"""Fetch the location for the callsign."""
|
|
||||||
LOG.debug(f"populate_callsign_location {callsign}")
|
|
||||||
if data:
|
|
||||||
location_data = _calculate_location_data(data)
|
|
||||||
callsign_locations[callsign] = location_data
|
|
||||||
send_location_data_to_browser(location_data)
|
|
||||||
return
|
|
||||||
|
|
||||||
# First we are going to try to get the location from aprs.fi
|
|
||||||
# if there is no internets, then this will fail and we will
|
|
||||||
# fallback to calling REPEAT for the location for the callsign.
|
|
||||||
fallback = False
|
|
||||||
if not CONF.aprs_fi.apiKey:
|
|
||||||
LOG.warning(
|
|
||||||
"Config aprs_fi.apiKey is not set. Can't get location from aprs.fi "
|
|
||||||
" falling back to sending REPEAT to get location.",
|
|
||||||
)
|
|
||||||
fallback = True
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
aprs_data = plugin_utils.get_aprs_fi(CONF.aprs_fi.apiKey, callsign)
|
|
||||||
if not len(aprs_data["entries"]):
|
|
||||||
LOG.error("Didn't get any entries from aprs.fi")
|
|
||||||
return
|
|
||||||
lat = float(aprs_data["entries"][0]["lat"])
|
|
||||||
lon = float(aprs_data["entries"][0]["lng"])
|
|
||||||
try: # altitude not always provided
|
|
||||||
alt = float(aprs_data["entries"][0]["altitude"])
|
|
||||||
except Exception:
|
|
||||||
alt = 0
|
|
||||||
location_data = {
|
|
||||||
"callsign": callsign,
|
|
||||||
"lat": lat,
|
|
||||||
"lon": lon,
|
|
||||||
"altitude": alt,
|
|
||||||
"lasttime": int(aprs_data["entries"][0]["lasttime"]),
|
|
||||||
"course": float(aprs_data["entries"][0].get("course", 0)),
|
|
||||||
"speed": float(aprs_data["entries"][0].get("speed", 0)),
|
|
||||||
}
|
|
||||||
location_data = _calculate_location_data(location_data)
|
|
||||||
callsign_locations[callsign] = location_data
|
|
||||||
send_location_data_to_browser(location_data)
|
|
||||||
return
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(f"Failed to fetch aprs.fi '{ex}'")
|
|
||||||
LOG.error(ex)
|
|
||||||
fallback = True
|
|
||||||
|
|
||||||
if fallback:
|
|
||||||
# We don't have the location data
|
|
||||||
# and we can't get it from aprs.fi
|
|
||||||
# Send a special message to REPEAT to get the location data
|
|
||||||
LOG.info(f"Sending REPEAT to get location for callsign {callsign}.")
|
|
||||||
tx.send(
|
|
||||||
packets.MessagePacket(
|
|
||||||
from_call=CONF.callsign,
|
|
||||||
to_call="REPEAT",
|
|
||||||
message_text=f"ld {callsign}",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
|
|
||||||
"""Class that handles packets being sent to us."""
|
|
||||||
|
|
||||||
def __init__(self, packet_queue, socketio):
|
|
||||||
self.socketio = socketio
|
|
||||||
self.connected = False
|
|
||||||
super().__init__(packet_queue)
|
|
||||||
|
|
||||||
def process_ack_packet(self, packet: packets.AckPacket):
|
|
||||||
super().process_ack_packet(packet)
|
|
||||||
ack_num = packet.get("msgNo")
|
|
||||||
SentMessages().ack(ack_num)
|
|
||||||
msg = SentMessages().get(ack_num)
|
|
||||||
if msg:
|
|
||||||
self.socketio.emit(
|
|
||||||
"ack", msg,
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
self.got_ack = True
|
|
||||||
|
|
||||||
def process_our_message_packet(self, packet: packets.MessagePacket):
|
|
||||||
global callsign_locations
|
|
||||||
# ok lets see if we have the location for the
|
|
||||||
# person we just sent a message to.
|
|
||||||
from_call = packet.get("from_call").upper()
|
|
||||||
if from_call == "REPEAT":
|
|
||||||
# We got a message from REPEAT. Is this a location message?
|
|
||||||
message = packet.get("message_text")
|
|
||||||
if message.startswith("^ld^"):
|
|
||||||
location_data = _build_location_from_repeat(message)
|
|
||||||
callsign = location_data["callsign"]
|
|
||||||
location_data = _calculate_location_data(location_data)
|
|
||||||
callsign_locations[callsign] = location_data
|
|
||||||
send_location_data_to_browser(location_data)
|
|
||||||
return
|
|
||||||
elif (
|
|
||||||
from_call not in callsign_locations
|
|
||||||
and from_call not in callsign_no_track
|
|
||||||
and client_factory.create().transport() in [client.TRANSPORT_APRSIS, client.TRANSPORT_FAKE]
|
|
||||||
):
|
|
||||||
# We have to ask aprs for the location for the callsign
|
|
||||||
# We send a message packet to wb4bor-11 asking for location.
|
|
||||||
populate_callsign_location(from_call)
|
|
||||||
# Send the packet to the browser.
|
|
||||||
self.socketio.emit(
|
|
||||||
"new", packet.__dict__,
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LocationProcessingThread(aprsd_threads.APRSDThread):
|
|
||||||
"""Class to handle the location processing."""
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("LocationProcessingThread")
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _get_transport(stats):
|
|
||||||
if CONF.aprs_network.enabled:
|
|
||||||
transport = "aprs-is"
|
|
||||||
aprs_connection = (
|
|
||||||
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
|
||||||
"{}</a>".format(stats["APRSClientStats"]["server_string"])
|
|
||||||
)
|
|
||||||
elif kiss.KISSClient.is_enabled():
|
|
||||||
transport = kiss.KISSClient.transport()
|
|
||||||
if transport == client.TRANSPORT_TCPKISS:
|
|
||||||
aprs_connection = (
|
|
||||||
"TCPKISS://{}:{}".format(
|
|
||||||
CONF.kiss_tcp.host,
|
|
||||||
CONF.kiss_tcp.port,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif transport == client.TRANSPORT_SERIALKISS:
|
|
||||||
# for pep8 violation
|
|
||||||
aprs_connection = (
|
|
||||||
"SerialKISS://{}@{} baud".format(
|
|
||||||
CONF.kiss_serial.device,
|
|
||||||
CONF.kiss_serial.baudrate,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
elif CONF.fake_client.enabled:
|
|
||||||
transport = client.TRANSPORT_FAKE
|
|
||||||
aprs_connection = "Fake Client"
|
|
||||||
|
|
||||||
return transport, aprs_connection
|
|
||||||
|
|
||||||
|
|
||||||
@flask_app.route("/location/<callsign>", methods=["POST"])
|
|
||||||
def location(callsign):
|
|
||||||
LOG.debug(f"Fetch location for callsign {callsign}")
|
|
||||||
if not callsign in callsign_no_track:
|
|
||||||
populate_callsign_location(callsign)
|
|
||||||
|
|
||||||
|
|
||||||
@auth.login_required
|
|
||||||
@flask_app.route("/")
|
|
||||||
def index():
|
|
||||||
stats = _stats()
|
|
||||||
|
|
||||||
# For development
|
|
||||||
html_template = "index.html"
|
|
||||||
LOG.debug(f"Template {html_template}")
|
|
||||||
|
|
||||||
transport, aprs_connection = _get_transport(stats["stats"])
|
|
||||||
LOG.debug(f"transport {transport} aprs_connection {aprs_connection}")
|
|
||||||
|
|
||||||
stats["transport"] = transport
|
|
||||||
stats["aprs_connection"] = aprs_connection
|
|
||||||
LOG.debug(f"initial stats = {stats}")
|
|
||||||
latitude = CONF.webchat.latitude
|
|
||||||
if latitude:
|
|
||||||
latitude = float(CONF.webchat.latitude)
|
|
||||||
|
|
||||||
longitude = CONF.webchat.longitude
|
|
||||||
if longitude:
|
|
||||||
longitude = float(longitude)
|
|
||||||
|
|
||||||
return flask.render_template(
|
|
||||||
html_template,
|
|
||||||
initial_stats=stats,
|
|
||||||
aprs_connection=aprs_connection,
|
|
||||||
callsign=CONF.callsign,
|
|
||||||
version=aprsd.__version__,
|
|
||||||
latitude=latitude,
|
|
||||||
longitude=longitude,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@auth.login_required
|
|
||||||
@flask_app.route("/send-message-status")
|
|
||||||
def send_message_status():
|
|
||||||
LOG.debug(request)
|
|
||||||
msgs = SentMessages()
|
|
||||||
info = msgs.get_all()
|
|
||||||
return json.dumps(info)
|
|
||||||
|
|
||||||
|
|
||||||
def _stats():
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
|
|
||||||
time_format = "%m-%d-%Y %H:%M:%S"
|
|
||||||
stats_dict = stats.stats_collector.collect(serializable=True)
|
|
||||||
# Webchat doesnt need these
|
|
||||||
if "WatchList" in stats_dict:
|
|
||||||
del stats_dict["WatchList"]
|
|
||||||
if "SeenList" in stats_dict:
|
|
||||||
del stats_dict["SeenList"]
|
|
||||||
if "APRSDThreadList" in stats_dict:
|
|
||||||
del stats_dict["APRSDThreadList"]
|
|
||||||
if "PacketList" in stats_dict:
|
|
||||||
del stats_dict["PacketList"]
|
|
||||||
if "EmailStats" in stats_dict:
|
|
||||||
del stats_dict["EmailStats"]
|
|
||||||
if "PluginManager" in stats_dict:
|
|
||||||
del stats_dict["PluginManager"]
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"time": now.strftime(time_format),
|
|
||||||
"stats": stats_dict,
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@flask_app.route("/stats")
|
|
||||||
def get_stats():
|
|
||||||
return json.dumps(_stats())
|
|
||||||
|
|
||||||
|
|
||||||
class SendMessageNamespace(Namespace):
|
|
||||||
"""Class to handle the socketio interactions."""
|
|
||||||
got_ack = False
|
|
||||||
reply_sent = False
|
|
||||||
msg = None
|
|
||||||
request = None
|
|
||||||
|
|
||||||
def __init__(self, namespace=None, config=None):
|
|
||||||
super().__init__(namespace)
|
|
||||||
|
|
||||||
def on_connect(self):
|
|
||||||
global socketio
|
|
||||||
LOG.debug("Web socket connected")
|
|
||||||
socketio.emit(
|
|
||||||
"connected", {"data": "/sendmsg Connected"},
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_disconnect(self):
|
|
||||||
LOG.debug("WS Disconnected")
|
|
||||||
|
|
||||||
def on_send(self, data):
|
|
||||||
global socketio
|
|
||||||
LOG.debug(f"WS: on_send {data}")
|
|
||||||
self.request = data
|
|
||||||
data["from"] = CONF.callsign
|
|
||||||
path = data.get("path", None)
|
|
||||||
if not path:
|
|
||||||
path = []
|
|
||||||
elif "," in path:
|
|
||||||
path_opts = path.split(",")
|
|
||||||
path = [x.strip() for x in path_opts]
|
|
||||||
else:
|
|
||||||
path = [path]
|
|
||||||
|
|
||||||
pkt = packets.MessagePacket(
|
|
||||||
from_call=data["from"],
|
|
||||||
to_call=data["to"].upper(),
|
|
||||||
message_text=data["message"],
|
|
||||||
path=path,
|
|
||||||
)
|
|
||||||
pkt.prepare()
|
|
||||||
self.msg = pkt
|
|
||||||
msgs = SentMessages()
|
|
||||||
tx.send(pkt)
|
|
||||||
msgs.add(pkt)
|
|
||||||
msgs.set_status(pkt.msgNo, "Sending")
|
|
||||||
obj = msgs.get(pkt.msgNo)
|
|
||||||
socketio.emit(
|
|
||||||
"sent", obj,
|
|
||||||
namespace="/sendmsg",
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_gps(self, data):
|
|
||||||
LOG.debug(f"WS on_GPS: {data}")
|
|
||||||
lat = data["latitude"]
|
|
||||||
long = data["longitude"]
|
|
||||||
LOG.debug(f"Lat {lat}")
|
|
||||||
LOG.debug(f"Long {long}")
|
|
||||||
path = data.get("path", None)
|
|
||||||
if not path:
|
|
||||||
path = []
|
|
||||||
elif "," in path:
|
|
||||||
path_opts = path.split(",")
|
|
||||||
path = [x.strip() for x in path_opts]
|
|
||||||
else:
|
|
||||||
path = [path]
|
|
||||||
|
|
||||||
tx.send(
|
|
||||||
packets.BeaconPacket(
|
|
||||||
from_call=CONF.callsign,
|
|
||||||
to_call="APDW16",
|
|
||||||
latitude=lat,
|
|
||||||
longitude=long,
|
|
||||||
comment="APRSD WebChat Beacon",
|
|
||||||
path=path,
|
|
||||||
),
|
|
||||||
direct=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_message(self, data):
|
|
||||||
LOG.debug(f"WS Data {data}")
|
|
||||||
|
|
||||||
def handle_json(self, data):
|
|
||||||
LOG.debug(f"WS json {data}")
|
|
||||||
|
|
||||||
def on_get_callsign_location(self, data):
|
|
||||||
LOG.debug(f"on_callsign_location {data}")
|
|
||||||
if data["callsign"] not in callsign_no_track:
|
|
||||||
populate_callsign_location(data["callsign"])
|
|
||||||
|
|
||||||
|
|
||||||
@trace.trace
|
|
||||||
def init_flask(loglevel, quiet):
|
|
||||||
global socketio, flask_app
|
|
||||||
|
|
||||||
socketio = SocketIO(
|
|
||||||
flask_app, logger=False, engineio_logger=False,
|
|
||||||
async_mode="threading",
|
|
||||||
)
|
|
||||||
|
|
||||||
socketio.on_namespace(
|
|
||||||
SendMessageNamespace(
|
|
||||||
"/sendmsg",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return socketio
|
|
||||||
|
|
||||||
|
|
||||||
# main() ###
|
|
||||||
@cli.command()
|
|
||||||
@cli_helper.add_options(cli_helper.common_options)
|
|
||||||
@click.option(
|
|
||||||
"-f",
|
|
||||||
"--flush",
|
|
||||||
"flush",
|
|
||||||
is_flag=True,
|
|
||||||
show_default=True,
|
|
||||||
default=False,
|
|
||||||
help="Flush out all old aged messages on disk.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-p",
|
|
||||||
"--port",
|
|
||||||
"port",
|
|
||||||
show_default=True,
|
|
||||||
default=None,
|
|
||||||
help="Port to listen to web requests. This overrides the config.webchat.web_port setting.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
@cli_helper.process_standard_options
|
|
||||||
def webchat(ctx, flush, port):
|
|
||||||
"""Web based HAM Radio chat program!"""
|
|
||||||
loglevel = ctx.obj["loglevel"]
|
|
||||||
quiet = ctx.obj["quiet"]
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
|
|
||||||
level, msg = utils._check_version()
|
|
||||||
if level:
|
|
||||||
LOG.warning(msg)
|
|
||||||
else:
|
|
||||||
LOG.info(msg)
|
|
||||||
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
|
||||||
|
|
||||||
CONF.log_opt_values(logging.getLogger(), logging.DEBUG)
|
|
||||||
if not port:
|
|
||||||
port = CONF.webchat.web_port
|
|
||||||
|
|
||||||
# Initialize the client factory and create
|
|
||||||
# The correct client object ready for use
|
|
||||||
# Make sure we have 1 client transport enabled
|
|
||||||
if not client_factory.is_client_enabled():
|
|
||||||
LOG.error("No Clients are enabled in config.")
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
if not client_factory.is_client_configured():
|
|
||||||
LOG.error("APRS client is not properly configured in config file.")
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
# Creates the client object
|
|
||||||
LOG.info("Creating client connection")
|
|
||||||
aprs_client = client_factory.create()
|
|
||||||
LOG.info(aprs_client)
|
|
||||||
if not aprs_client.login_success:
|
|
||||||
# We failed to login, will just quit!
|
|
||||||
msg = f"Login Failure: {aprs_client.login_failure}"
|
|
||||||
LOG.error(msg)
|
|
||||||
print(msg)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
keepalive = keep_alive.KeepAliveThread()
|
|
||||||
LOG.info("Start KeepAliveThread")
|
|
||||||
keepalive.start()
|
|
||||||
|
|
||||||
stats_store_thread = stats_thread.APRSDStatsStoreThread()
|
|
||||||
stats_store_thread.start()
|
|
||||||
|
|
||||||
socketio = init_flask(loglevel, quiet)
|
|
||||||
rx_thread = rx.APRSDPluginRXThread(
|
|
||||||
packet_queue=threads.packet_queue,
|
|
||||||
)
|
|
||||||
rx_thread.start()
|
|
||||||
process_thread = WebChatProcessPacketThread(
|
|
||||||
packet_queue=threads.packet_queue,
|
|
||||||
socketio=socketio,
|
|
||||||
)
|
|
||||||
process_thread.start()
|
|
||||||
|
|
||||||
LOG.info("Start socketio.run()")
|
|
||||||
socketio.run(
|
|
||||||
flask_app,
|
|
||||||
# This is broken for now after removing cryptography
|
|
||||||
# and pyopenssl
|
|
||||||
# ssl_context="adhoc",
|
|
||||||
host=CONF.webchat.web_ip,
|
|
||||||
port=port,
|
|
||||||
allow_unsafe_werkzeug=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
LOG.info("WebChat exiting!!!! Bye.")
|
|
@ -11,17 +11,12 @@ watch_list_group = cfg.OptGroup(
|
|||||||
name="watch_list",
|
name="watch_list",
|
||||||
title="Watch List settings",
|
title="Watch List settings",
|
||||||
)
|
)
|
||||||
webchat_group = cfg.OptGroup(
|
|
||||||
name="webchat",
|
|
||||||
title="Settings specific to the webchat command",
|
|
||||||
)
|
|
||||||
|
|
||||||
registry_group = cfg.OptGroup(
|
registry_group = cfg.OptGroup(
|
||||||
name="aprs_registry",
|
name="aprs_registry",
|
||||||
title="APRS Registry settings",
|
title="APRS Registry settings",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
aprsd_opts = [
|
aprsd_opts = [
|
||||||
cfg.StrOpt(
|
cfg.StrOpt(
|
||||||
"callsign",
|
"callsign",
|
||||||
@ -194,34 +189,6 @@ enabled_plugins_opts = [
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
webchat_opts = [
|
|
||||||
cfg.StrOpt(
|
|
||||||
"web_ip",
|
|
||||||
default="0.0.0.0",
|
|
||||||
help="The ip address to listen on",
|
|
||||||
),
|
|
||||||
cfg.PortOpt(
|
|
||||||
"web_port",
|
|
||||||
default=8001,
|
|
||||||
help="The port to listen on",
|
|
||||||
),
|
|
||||||
cfg.StrOpt(
|
|
||||||
"latitude",
|
|
||||||
default=None,
|
|
||||||
help="Latitude for the GPS Beacon button. If not set, the button will not be enabled.",
|
|
||||||
),
|
|
||||||
cfg.StrOpt(
|
|
||||||
"longitude",
|
|
||||||
default=None,
|
|
||||||
help="Longitude for the GPS Beacon button. If not set, the button will not be enabled.",
|
|
||||||
),
|
|
||||||
cfg.BoolOpt(
|
|
||||||
"disable_url_request_logging",
|
|
||||||
default=False,
|
|
||||||
help="Disable the logging of url requests in the webchat command.",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
registry_opts = [
|
registry_opts = [
|
||||||
cfg.BoolOpt(
|
cfg.BoolOpt(
|
||||||
"enabled",
|
"enabled",
|
||||||
@ -261,8 +228,6 @@ def register_opts(config):
|
|||||||
config.register_opts(enabled_plugins_opts)
|
config.register_opts(enabled_plugins_opts)
|
||||||
config.register_group(watch_list_group)
|
config.register_group(watch_list_group)
|
||||||
config.register_opts(watch_list_opts, group=watch_list_group)
|
config.register_opts(watch_list_opts, group=watch_list_group)
|
||||||
config.register_group(webchat_group)
|
|
||||||
config.register_opts(webchat_opts, group=webchat_group)
|
|
||||||
config.register_group(registry_group)
|
config.register_group(registry_group)
|
||||||
config.register_opts(registry_opts, group=registry_group)
|
config.register_opts(registry_opts, group=registry_group)
|
||||||
|
|
||||||
@ -271,6 +236,5 @@ def list_opts():
|
|||||||
return {
|
return {
|
||||||
"DEFAULT": (aprsd_opts + enabled_plugins_opts),
|
"DEFAULT": (aprsd_opts + enabled_plugins_opts),
|
||||||
watch_list_group.name: watch_list_opts,
|
watch_list_group.name: watch_list_opts,
|
||||||
webchat_group.name: webchat_opts,
|
|
||||||
registry_group.name: registry_opts,
|
registry_group.name: registry_opts,
|
||||||
}
|
}
|
||||||
|
@ -68,19 +68,9 @@ def setup_logging(loglevel=None, quiet=False):
|
|||||||
"aprslib.parsing",
|
"aprslib.parsing",
|
||||||
"aprslib.exceptions",
|
"aprslib.exceptions",
|
||||||
]
|
]
|
||||||
webserver_list = [
|
|
||||||
"werkzeug",
|
|
||||||
"werkzeug._internal",
|
|
||||||
"socketio",
|
|
||||||
"urllib3.connectionpool",
|
|
||||||
"chardet",
|
|
||||||
"chardet.charsetgroupprober",
|
|
||||||
"chardet.eucjpprober",
|
|
||||||
"chardet.mbcharsetprober",
|
|
||||||
]
|
|
||||||
|
|
||||||
# We don't really want to see the aprslib parsing debug output.
|
# We don't really want to see the aprslib parsing debug output.
|
||||||
disable_list = imap_list + aprslib_list + webserver_list
|
disable_list = imap_list + aprslib_list
|
||||||
|
|
||||||
# remove every other logger's handlers
|
# remove every other logger's handlers
|
||||||
# and propagate to root logger
|
# and propagate to root logger
|
||||||
@ -91,12 +81,6 @@ def setup_logging(loglevel=None, quiet=False):
|
|||||||
else:
|
else:
|
||||||
logging.getLogger(name).propagate = True
|
logging.getLogger(name).propagate = True
|
||||||
|
|
||||||
if CONF.webchat.disable_url_request_logging:
|
|
||||||
for name in webserver_list:
|
|
||||||
logging.getLogger(name).handlers = []
|
|
||||||
logging.getLogger(name).propagate = True
|
|
||||||
logging.getLogger(name).setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
handlers = [
|
handlers = [
|
||||||
{
|
{
|
||||||
"sink": sys.stdout,
|
"sink": sys.stdout,
|
||||||
|
@ -55,7 +55,7 @@ def cli(ctx):
|
|||||||
def load_commands():
|
def load_commands():
|
||||||
from .cmds import ( # noqa
|
from .cmds import ( # noqa
|
||||||
completion, dev, fetch_stats, healthcheck, list_plugins, listen,
|
completion, dev, fetch_stats, healthcheck, list_plugins, listen,
|
||||||
send_message, server, webchat,
|
send_message, server,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
body {
|
|
||||||
background: #eeeeee;
|
|
||||||
margin: 2em;
|
|
||||||
text-align: center;
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 2em;
|
|
||||||
text-align: center;
|
|
||||||
height: 10vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.segment {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
|
|
||||||
#graphs {
|
|
||||||
display: grid;
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
#graphs_center {
|
|
||||||
display: block;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
#left {
|
|
||||||
margin-right: 2px;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
#right {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
#center {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
#packetsChart, #messageChart, #emailChart, #memChart {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
#stats {
|
|
||||||
margin: auto;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
#jsonstats {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#title {
|
|
||||||
font-size: 4em;
|
|
||||||
}
|
|
||||||
#version{
|
|
||||||
font-size: .5em;
|
|
||||||
}
|
|
||||||
#uptime, #aprsis {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
#callsign {
|
|
||||||
font-size: 1.4em;
|
|
||||||
color: #00F;
|
|
||||||
padding-top: 8px;
|
|
||||||
margin:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title_rx {
|
|
||||||
background-color: darkseagreen;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title_tx {
|
|
||||||
background-color: lightcoral;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aprsd_1 {
|
|
||||||
background-image: url(/static/images/aprs-symbols-16-0.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: -160px -48px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
/* PrismJS 1.29.0
|
|
||||||
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+json+json5+log&plugins=show-language+toolbar */
|
|
||||||
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
|
|
||||||
div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
|
|
@ -1,35 +0,0 @@
|
|||||||
/* Style the tab */
|
|
||||||
.tab {
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style the buttons that are used to open the tab content */
|
|
||||||
.tab button {
|
|
||||||
background-color: inherit;
|
|
||||||
float: left;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 14px 16px;
|
|
||||||
transition: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Change background color of buttons on hover */
|
|
||||||
.tab button:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create an active/current tablink class */
|
|
||||||
.tab button.active {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style the tab content */
|
|
||||||
.tabcontent {
|
|
||||||
display: none;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
Binary file not shown.
Before ![]() (image error) Size: 37 KiB |
Binary file not shown.
Before ![]() (image error) Size: 52 KiB |
Binary file not shown.
Before ![]() (image error) Size: 48 KiB |
Binary file not shown.
Before ![]() (image error) Size: 52 KiB |
Binary file not shown.
Before ![]() (image error) Size: 48 KiB |
Binary file not shown.
Before ![]() (image error) Size: 40 KiB |
@ -1,235 +0,0 @@
|
|||||||
var packet_list = {};
|
|
||||||
|
|
||||||
window.chartColors = {
|
|
||||||
red: 'rgb(255, 99, 132)',
|
|
||||||
orange: 'rgb(255, 159, 64)',
|
|
||||||
yellow: 'rgb(255, 205, 86)',
|
|
||||||
green: 'rgb(26, 181, 77)',
|
|
||||||
blue: 'rgb(54, 162, 235)',
|
|
||||||
purple: 'rgb(153, 102, 255)',
|
|
||||||
grey: 'rgb(201, 203, 207)',
|
|
||||||
black: 'rgb(0, 0, 0)',
|
|
||||||
lightcoral: 'rgb(240,128,128)',
|
|
||||||
darkseagreen: 'rgb(143, 188,143)'
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
function size_dict(d){c=0; for (i in d) ++c; return c}
|
|
||||||
|
|
||||||
function start_charts() {
|
|
||||||
Chart.scaleService.updateScaleDefaults('linear', {
|
|
||||||
ticks: {
|
|
||||||
min: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
packets_chart = new Chart($("#packetsChart"), {
|
|
||||||
label: 'APRS Packets',
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'Packets Sent',
|
|
||||||
borderColor: window.chartColors.lightcoral,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Packets Recieved',
|
|
||||||
borderColor: window.chartColors.darkseagreen,
|
|
||||||
data: [],
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'APRS Packets',
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'timeseries',
|
|
||||||
offset: true,
|
|
||||||
ticks: {
|
|
||||||
major: { enabled: true },
|
|
||||||
fontStyle: context => context.tick.major ? 'bold' : undefined,
|
|
||||||
source: 'data',
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkip: true,
|
|
||||||
autoSkipPadding: 75,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
message_chart = new Chart($("#messageChart"), {
|
|
||||||
label: 'Messages',
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'Messages Sent',
|
|
||||||
borderColor: window.chartColors.lightcoral,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Messages Recieved',
|
|
||||||
borderColor: window.chartColors.darkseagreen,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ack Sent',
|
|
||||||
borderColor: window.chartColors.purple,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ack Recieved',
|
|
||||||
borderColor: window.chartColors.black,
|
|
||||||
data: [],
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'APRS Messages',
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'timeseries',
|
|
||||||
offset: true,
|
|
||||||
ticks: {
|
|
||||||
major: { enabled: true },
|
|
||||||
fontStyle: context => context.tick.major ? 'bold' : undefined,
|
|
||||||
source: 'data',
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkip: true,
|
|
||||||
autoSkipPadding: 75,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
email_chart = new Chart($("#emailChart"), {
|
|
||||||
label: 'Email Messages',
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'Sent',
|
|
||||||
borderColor: window.chartColors.lightcoral,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Recieved',
|
|
||||||
borderColor: window.chartColors.darkseagreen,
|
|
||||||
data: [],
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Email Messages',
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'timeseries',
|
|
||||||
offset: true,
|
|
||||||
ticks: {
|
|
||||||
major: { enabled: true },
|
|
||||||
fontStyle: context => context.tick.major ? 'bold' : undefined,
|
|
||||||
source: 'data',
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkip: true,
|
|
||||||
autoSkipPadding: 75,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
memory_chart = new Chart($("#memChart"), {
|
|
||||||
label: 'Memory Usage',
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'Peak Ram usage',
|
|
||||||
borderColor: window.chartColors.red,
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Current Ram usage',
|
|
||||||
borderColor: window.chartColors.blue,
|
|
||||||
data: [],
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Memory Usage',
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'timeseries',
|
|
||||||
offset: true,
|
|
||||||
ticks: {
|
|
||||||
major: { enabled: true },
|
|
||||||
fontStyle: context => context.tick.major ? 'bold' : undefined,
|
|
||||||
source: 'data',
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkip: true,
|
|
||||||
autoSkipPadding: 75,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addData(chart, label, newdata) {
|
|
||||||
chart.data.labels.push(label);
|
|
||||||
chart.data.datasets.forEach((dataset) => {
|
|
||||||
dataset.data.push(newdata);
|
|
||||||
});
|
|
||||||
chart.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDualData(chart, label, first, second) {
|
|
||||||
chart.data.labels.push(label);
|
|
||||||
chart.data.datasets[0].data.push(first);
|
|
||||||
chart.data.datasets[1].data.push(second);
|
|
||||||
chart.update();
|
|
||||||
}
|
|
||||||
function updateQuadData(chart, label, first, second, third, fourth) {
|
|
||||||
chart.data.labels.push(label);
|
|
||||||
chart.data.datasets[0].data.push(first);
|
|
||||||
chart.data.datasets[1].data.push(second);
|
|
||||||
chart.data.datasets[2].data.push(third);
|
|
||||||
chart.data.datasets[3].data.push(fourth);
|
|
||||||
chart.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_stats( data ) {
|
|
||||||
our_callsign = data["APRSDStats"]["callsign"];
|
|
||||||
$("#version").text( data["APRSDStats"]["version"] );
|
|
||||||
$("#aprs_connection").html( data["aprs_connection"] );
|
|
||||||
$("#uptime").text( "uptime: " + data["APRSDStats"]["uptime"] );
|
|
||||||
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
|
||||||
$("#jsonstats").html(html_pretty);
|
|
||||||
short_time = data["time"].split(/\s(.+)/)[1];
|
|
||||||
packet_list = data["PacketList"]["packets"];
|
|
||||||
updateDualData(packets_chart, short_time, data["PacketList"]["sent"], data["PacketList"]["received"]);
|
|
||||||
updateQuadData(message_chart, short_time, packet_list["MessagePacket"]["tx"], packet_list["MessagePacket"]["rx"],
|
|
||||||
packet_list["AckPacket"]["tx"], packet_list["AckPacket"]["rx"]);
|
|
||||||
updateDualData(email_chart, short_time, data["EmailStats"]["sent"], data["EmailStats"]["recieved"]);
|
|
||||||
updateDualData(memory_chart, short_time, data["APRSDStats"]["memory_peak"], data["APRSDStats"]["memory_current"]);
|
|
||||||
}
|
|
@ -1,465 +0,0 @@
|
|||||||
var packet_list = {};
|
|
||||||
|
|
||||||
var tx_data = [];
|
|
||||||
var rx_data = [];
|
|
||||||
|
|
||||||
var packet_types_data = {};
|
|
||||||
|
|
||||||
var mem_current = []
|
|
||||||
var mem_peak = []
|
|
||||||
|
|
||||||
var thread_current = []
|
|
||||||
|
|
||||||
|
|
||||||
function start_charts() {
|
|
||||||
console.log("start_charts() called");
|
|
||||||
// Initialize the echarts instance based on the prepared dom
|
|
||||||
create_packets_chart();
|
|
||||||
create_packets_types_chart();
|
|
||||||
create_messages_chart();
|
|
||||||
create_ack_chart();
|
|
||||||
create_memory_chart();
|
|
||||||
create_thread_chart();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function create_packets_chart() {
|
|
||||||
// The packets totals TX/RX chart.
|
|
||||||
pkt_c_canvas = document.getElementById('packetsChart');
|
|
||||||
packets_chart = echarts.init(pkt_c_canvas);
|
|
||||||
|
|
||||||
// Specify the configuration items and data for the chart
|
|
||||||
var option = {
|
|
||||||
title: {
|
|
||||||
text: 'APRS Packet totals'
|
|
||||||
},
|
|
||||||
legend: {},
|
|
||||||
tooltip : {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show : true,
|
|
||||||
feature : {
|
|
||||||
mark : {show: true},
|
|
||||||
dataView : {show: true, readOnly: true},
|
|
||||||
magicType : {show: true, type: ['line', 'bar']},
|
|
||||||
restore : {show: true},
|
|
||||||
saveAsImage : {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculable : true,
|
|
||||||
xAxis: { type: 'time' },
|
|
||||||
yAxis: { },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'tx',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
color: 'red',
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'tx' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
name: 'rx',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'rx'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Display the chart using the configuration items and data just specified.
|
|
||||||
packets_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function create_packets_types_chart() {
|
|
||||||
// The packets types chart
|
|
||||||
pkt_types_canvas = document.getElementById('packetTypesChart');
|
|
||||||
packet_types_chart = echarts.init(pkt_types_canvas);
|
|
||||||
|
|
||||||
// The series and data are built and updated on the fly
|
|
||||||
// as packets come in.
|
|
||||||
var option = {
|
|
||||||
title: {
|
|
||||||
text: 'Packet Types'
|
|
||||||
},
|
|
||||||
legend: {},
|
|
||||||
tooltip : {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show : true,
|
|
||||||
feature : {
|
|
||||||
mark : {show: true},
|
|
||||||
dataView : {show: true, readOnly: true},
|
|
||||||
magicType : {show: true, type: ['line', 'bar']},
|
|
||||||
restore : {show: true},
|
|
||||||
saveAsImage : {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculable : true,
|
|
||||||
xAxis: { type: 'time' },
|
|
||||||
yAxis: { },
|
|
||||||
}
|
|
||||||
|
|
||||||
packet_types_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function create_messages_chart() {
|
|
||||||
msg_c_canvas = document.getElementById('messagesChart');
|
|
||||||
message_chart = echarts.init(msg_c_canvas);
|
|
||||||
|
|
||||||
// Specify the configuration items and data for the chart
|
|
||||||
var option = {
|
|
||||||
title: {
|
|
||||||
text: 'Message Packets'
|
|
||||||
},
|
|
||||||
legend: {},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show: true,
|
|
||||||
feature: {
|
|
||||||
mark : {show: true},
|
|
||||||
dataView : {show: true, readOnly: true},
|
|
||||||
magicType : {show: true, type: ['line', 'bar']},
|
|
||||||
restore : {show: true},
|
|
||||||
saveAsImage : {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculable: true,
|
|
||||||
xAxis: { type: 'time' },
|
|
||||||
yAxis: { },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'tx',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
color: 'red',
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'tx' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
name: 'rx',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'rx'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Display the chart using the configuration items and data just specified.
|
|
||||||
message_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_ack_chart() {
|
|
||||||
ack_canvas = document.getElementById('acksChart');
|
|
||||||
ack_chart = echarts.init(ack_canvas);
|
|
||||||
|
|
||||||
// Specify the configuration items and data for the chart
|
|
||||||
var option = {
|
|
||||||
title: {
|
|
||||||
text: 'Ack Packets'
|
|
||||||
},
|
|
||||||
legend: {},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show: true,
|
|
||||||
feature: {
|
|
||||||
mark : {show: true},
|
|
||||||
dataView : {show: true, readOnly: false},
|
|
||||||
magicType : {show: true, type: ['line', 'bar']},
|
|
||||||
restore : {show: true},
|
|
||||||
saveAsImage : {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculable: true,
|
|
||||||
xAxis: { type: 'time' },
|
|
||||||
yAxis: { },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'tx',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
color: 'red',
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'tx' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
name: 'rx',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'rx'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
ack_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_memory_chart() {
|
|
||||||
ack_canvas = document.getElementById('memChart');
|
|
||||||
memory_chart = echarts.init(ack_canvas);
|
|
||||||
|
|
||||||
// Specify the configuration items and data for the chart
|
|
||||||
var option = {
|
|
||||||
title: {
|
|
||||||
text: 'Memory Usage'
|
|
||||||
},
|
|
||||||
legend: {},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show: true,
|
|
||||||
feature: {
|
|
||||||
mark : {show: true},
|
|
||||||
dataView : {show: true, readOnly: false},
|
|
||||||
magicType : {show: true, type: ['line', 'bar']},
|
|
||||||
restore : {show: true},
|
|
||||||
saveAsImage : {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculable: true,
|
|
||||||
xAxis: { type: 'time' },
|
|
||||||
yAxis: { },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'current',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
color: 'red',
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'current' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
name: 'peak',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'peak'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
memory_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_thread_chart() {
|
|
||||||
thread_canvas = document.getElementById('threadChart');
|
|
||||||
thread_chart = echarts.init(thread_canvas);
|
|
||||||
|
|
||||||
// Specify the configuration items and data for the chart
|
|
||||||
var option = {
|
|
||||||
title: {
|
|
||||||
text: 'Active Threads'
|
|
||||||
},
|
|
||||||
legend: {},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show: true,
|
|
||||||
feature: {
|
|
||||||
mark : {show: true},
|
|
||||||
dataView : {show: true, readOnly: false},
|
|
||||||
magicType : {show: true, type: ['line', 'bar']},
|
|
||||||
restore : {show: true},
|
|
||||||
saveAsImage : {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculable: true,
|
|
||||||
xAxis: { type: 'time' },
|
|
||||||
yAxis: { },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'current',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
color: 'red',
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: 'current' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function updatePacketData(chart, time, first, second) {
|
|
||||||
tx_data.push([time, first]);
|
|
||||||
rx_data.push([time, second]);
|
|
||||||
option = {
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'tx',
|
|
||||||
data: tx_data,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rx',
|
|
||||||
data: rx_data,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePacketTypesData(time, typesdata) {
|
|
||||||
//The options series is created on the fly each time based on
|
|
||||||
//the packet types we have in the data
|
|
||||||
var series = []
|
|
||||||
|
|
||||||
for (const k in typesdata) {
|
|
||||||
tx = [time, typesdata[k]["tx"]]
|
|
||||||
rx = [time, typesdata[k]["rx"]]
|
|
||||||
|
|
||||||
if (packet_types_data.hasOwnProperty(k)) {
|
|
||||||
packet_types_data[k]["tx"].push(tx)
|
|
||||||
packet_types_data[k]["rx"].push(rx)
|
|
||||||
} else {
|
|
||||||
packet_types_data[k] = {'tx': [tx], 'rx': [rx]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePacketTypesChart() {
|
|
||||||
series = []
|
|
||||||
for (const k in packet_types_data) {
|
|
||||||
entry = {
|
|
||||||
name: k+"tx",
|
|
||||||
data: packet_types_data[k]["tx"],
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: k+'tx' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
series.push(entry)
|
|
||||||
entry = {
|
|
||||||
name: k+"rx",
|
|
||||||
data: packet_types_data[k]["rx"],
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
encode: {
|
|
||||||
x: 'timestamp',
|
|
||||||
y: k+'rx' // refer sensor 1 value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
series.push(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
option = {
|
|
||||||
series: series
|
|
||||||
}
|
|
||||||
packet_types_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTypeChart(chart, key) {
|
|
||||||
//Generic function to update a packet type chart
|
|
||||||
if (! packet_types_data.hasOwnProperty(key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! packet_types_data[key].hasOwnProperty('tx')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var option = {
|
|
||||||
series: [{
|
|
||||||
name: "tx",
|
|
||||||
data: packet_types_data[key]["tx"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "rx",
|
|
||||||
data: packet_types_data[key]["rx"]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMemChart(time, current, peak) {
|
|
||||||
mem_current.push([time, current]);
|
|
||||||
mem_peak.push([time, peak]);
|
|
||||||
option = {
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'current',
|
|
||||||
data: mem_current,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'peak',
|
|
||||||
data: mem_peak,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
memory_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateThreadChart(time, threads) {
|
|
||||||
keys = Object.keys(threads);
|
|
||||||
thread_count = keys.length;
|
|
||||||
thread_current.push([time, thread_count]);
|
|
||||||
option = {
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'current',
|
|
||||||
data: thread_current,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
thread_chart.setOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMessagesChart() {
|
|
||||||
updateTypeChart(message_chart, "MessagePacket")
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAcksChart() {
|
|
||||||
updateTypeChart(ack_chart, "AckPacket")
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_stats( data ) {
|
|
||||||
console.log("update_stats() echarts.js called")
|
|
||||||
stats = data["stats"];
|
|
||||||
our_callsign = stats["APRSDStats"]["callsign"];
|
|
||||||
$("#version").text( stats["APRSDStats"]["version"] );
|
|
||||||
$("#aprs_connection").html( stats["aprs_connection"] );
|
|
||||||
$("#uptime").text( "uptime: " + stats["APRSDStats"]["uptime"] );
|
|
||||||
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
|
||||||
$("#jsonstats").html(html_pretty);
|
|
||||||
|
|
||||||
t = Date.parse(data["time"]);
|
|
||||||
ts = new Date(t);
|
|
||||||
updatePacketData(packets_chart, ts, stats["PacketList"]["tx"], stats["PacketList"]["rx"]);
|
|
||||||
updatePacketTypesData(ts, stats["PacketList"]["types"]);
|
|
||||||
updatePacketTypesChart();
|
|
||||||
updateMessagesChart();
|
|
||||||
updateAcksChart();
|
|
||||||
updateMemChart(ts, stats["APRSDStats"]["memory_current"], stats["APRSDStats"]["memory_peak"]);
|
|
||||||
updateThreadChart(ts, stats["APRSDThreadList"]);
|
|
||||||
//updateQuadData(message_chart, short_time, data["stats"]["messages"]["sent"], data["stats"]["messages"]["received"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]);
|
|
||||||
//updateDualData(email_chart, short_time, data["stats"]["email"]["sent"], data["stats"]["email"]["recieved"]);
|
|
||||||
//updateDualData(memory_chart, short_time, data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]);
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
function init_logs() {
|
|
||||||
const socket = io("/logs");
|
|
||||||
socket.on('connect', function () {
|
|
||||||
console.log("Connected to logs socketio");
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('connected', function(msg) {
|
|
||||||
console.log("Connected to /logs");
|
|
||||||
console.log(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('log_entry', function(data) {
|
|
||||||
update_logs(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function update_logs(data) {
|
|
||||||
var code_block = $('#logtext')
|
|
||||||
entry = data["message"]
|
|
||||||
const html_pretty = Prism.highlight(entry, Prism.languages.log, 'log');
|
|
||||||
code_block.append(html_pretty + "<br>");
|
|
||||||
var div = document.getElementById('logContainer');
|
|
||||||
div.scrollTop = div.scrollHeight;
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
// watchlist is a dict of ham callsign => symbol, packets
|
|
||||||
var watchlist = {};
|
|
||||||
var our_callsign = "";
|
|
||||||
|
|
||||||
function aprs_img(item, x_offset, y_offset) {
|
|
||||||
var x = x_offset * -16;
|
|
||||||
if (y_offset > 5) {
|
|
||||||
y_offset = 5;
|
|
||||||
}
|
|
||||||
var y = y_offset * -16;
|
|
||||||
var loc = x + 'px '+ y + 'px'
|
|
||||||
item.css('background-position', loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
function show_aprs_icon(item, symbol) {
|
|
||||||
var offset = ord(symbol) - 33;
|
|
||||||
var col = Math.floor(offset / 16);
|
|
||||||
var row = offset % 16;
|
|
||||||
//console.log("'" + symbol+"' off: "+offset+" row: "+ row + " col: " + col)
|
|
||||||
aprs_img(item, row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ord(str){return str.charCodeAt(0);}
|
|
||||||
|
|
||||||
|
|
||||||
function update_watchlist( data ) {
|
|
||||||
// Update the watch list
|
|
||||||
stats = data["stats"];
|
|
||||||
if (stats.hasOwnProperty("WatchList") == false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var watchdiv = $("#watchDiv");
|
|
||||||
var html_str = '<table class="ui celled striped table"><thead><tr><th>HAM Callsign</th><th>Age since last seen by APRSD</th></tr></thead><tbody>'
|
|
||||||
watchdiv.html('')
|
|
||||||
jQuery.each(stats["WatchList"], function(i, val) {
|
|
||||||
html_str += '<tr><td class="collapsing"><img id="callsign_'+i+'" class="aprsd_1"></img>' + i + '</td><td>' + val["last"] + '</td></tr>'
|
|
||||||
});
|
|
||||||
html_str += "</tbody></table>";
|
|
||||||
watchdiv.append(html_str);
|
|
||||||
|
|
||||||
jQuery.each(watchlist, function(i, val) {
|
|
||||||
//update the symbol
|
|
||||||
var call_img = $('#callsign_'+i);
|
|
||||||
show_aprs_icon(call_img, val['symbol'])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_watchlist_from_packet(callsign, val) {
|
|
||||||
if (!watchlist.hasOwnProperty(callsign)) {
|
|
||||||
watchlist[callsign] = {
|
|
||||||
"symbol": '[',
|
|
||||||
"packets": {},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (val.hasOwnProperty('symbol')) {
|
|
||||||
//console.log("Updating symbol for "+callsign + " to "+val["symbol"])
|
|
||||||
watchlist[callsign]["symbol"] = val["symbol"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (watchlist[callsign]["packets"].hasOwnProperty(val['ts']) == false) {
|
|
||||||
watchlist[callsign]["packets"][val['ts']]= val;
|
|
||||||
}
|
|
||||||
//console.log(watchlist)
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_seenlist( data ) {
|
|
||||||
stats = data["stats"];
|
|
||||||
if (stats.hasOwnProperty("SeenList") == false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var seendiv = $("#seenDiv");
|
|
||||||
var html_str = '<table class="ui celled striped table">'
|
|
||||||
html_str += '<thead><tr><th>HAM Callsign</th><th>Age since last seen by APRSD</th>'
|
|
||||||
html_str += '<th>Number of packets RX</th></tr></thead><tbody>'
|
|
||||||
seendiv.html('')
|
|
||||||
var seen_list = stats["SeenList"]
|
|
||||||
var len = Object.keys(seen_list).length
|
|
||||||
$('#seen_count').html(len)
|
|
||||||
jQuery.each(seen_list, function(i, val) {
|
|
||||||
html_str += '<tr><td class="collapsing">'
|
|
||||||
html_str += '<img id="callsign_'+i+'" class="aprsd_1"></img>' + i + '</td>'
|
|
||||||
html_str += '<td>' + val["last"] + '</td>'
|
|
||||||
html_str += '<td>' + val["count"] + '</td></tr>'
|
|
||||||
});
|
|
||||||
html_str += "</tbody></table>";
|
|
||||||
seendiv.append(html_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_plugins( data ) {
|
|
||||||
stats = data["stats"];
|
|
||||||
if (stats.hasOwnProperty("PluginManager") == false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var plugindiv = $("#pluginDiv");
|
|
||||||
var html_str = '<table class="ui celled striped table"><thead><tr>'
|
|
||||||
html_str += '<th>Plugin Name</th><th>Plugin Enabled?</th>'
|
|
||||||
html_str += '<th>Processed Packets</th><th>Sent Packets</th>'
|
|
||||||
html_str += '<th>Version</th>'
|
|
||||||
html_str += '</tr></thead><tbody>'
|
|
||||||
plugindiv.html('')
|
|
||||||
|
|
||||||
var plugins = stats["PluginManager"];
|
|
||||||
var keys = Object.keys(plugins);
|
|
||||||
keys.sort();
|
|
||||||
for (var i=0; i<keys.length; i++) { // now lets iterate in sort order
|
|
||||||
var key = keys[i];
|
|
||||||
var val = plugins[key];
|
|
||||||
html_str += '<tr><td class="collapsing">' + key + '</td>';
|
|
||||||
html_str += '<td>' + val["enabled"] + '</td><td>' + val["rx"] + '</td>';
|
|
||||||
html_str += '<td>' + val["tx"] + '</td><td>' + val["version"] +'</td></tr>';
|
|
||||||
}
|
|
||||||
html_str += "</tbody></table>";
|
|
||||||
plugindiv.append(html_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_threads( data ) {
|
|
||||||
stats = data["stats"];
|
|
||||||
if (stats.hasOwnProperty("APRSDThreadList") == false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var threadsdiv = $("#threadsDiv");
|
|
||||||
var countdiv = $("#thread_count");
|
|
||||||
var html_str = '<table class="ui celled striped table"><thead><tr>'
|
|
||||||
html_str += '<th>Thread Name</th><th>Alive?</th>'
|
|
||||||
html_str += '<th>Age</th><th>Loop Count</th>'
|
|
||||||
html_str += '</tr></thead><tbody>'
|
|
||||||
threadsdiv.html('')
|
|
||||||
|
|
||||||
var threads = stats["APRSDThreadList"];
|
|
||||||
var keys = Object.keys(threads);
|
|
||||||
countdiv.html(keys.length);
|
|
||||||
keys.sort();
|
|
||||||
for (var i=0; i<keys.length; i++) { // now lets iterate in sort order
|
|
||||||
var key = keys[i];
|
|
||||||
var val = threads[key];
|
|
||||||
html_str += '<tr><td class="collapsing">' + key + '</td>';
|
|
||||||
html_str += '<td>' + val["alive"] + '</td><td>' + val["age"] + '</td>';
|
|
||||||
html_str += '<td>' + val["loop_count"] + '</td></tr>';
|
|
||||||
}
|
|
||||||
html_str += "</tbody></table>";
|
|
||||||
threadsdiv.append(html_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_packets( data ) {
|
|
||||||
var packetsdiv = $("#packetsDiv");
|
|
||||||
//nuke the contents first, then add to it.
|
|
||||||
if (size_dict(packet_list) == 0 && size_dict(data) > 0) {
|
|
||||||
packetsdiv.html('')
|
|
||||||
}
|
|
||||||
jQuery.each(data.packets, function(i, val) {
|
|
||||||
pkt = val;
|
|
||||||
|
|
||||||
update_watchlist_from_packet(pkt['from_call'], pkt);
|
|
||||||
if ( packet_list.hasOwnProperty(pkt['timestamp']) == false ) {
|
|
||||||
// Store the packet
|
|
||||||
packet_list[pkt['timestamp']] = pkt;
|
|
||||||
//ts_str = val["timestamp"].toString();
|
|
||||||
//ts = ts_str.split(".")[0]*1000;
|
|
||||||
ts = pkt['timestamp'] * 1000;
|
|
||||||
var d = new Date(ts).toLocaleDateString();
|
|
||||||
var t = new Date(ts).toLocaleTimeString();
|
|
||||||
var from_call = pkt.from_call;
|
|
||||||
if (from_call == our_callsign) {
|
|
||||||
title_id = 'title_tx';
|
|
||||||
} else {
|
|
||||||
title_id = 'title_rx';
|
|
||||||
}
|
|
||||||
var from_to = d + " " + t + " " + from_call + " > "
|
|
||||||
|
|
||||||
if (val.hasOwnProperty('addresse')) {
|
|
||||||
from_to = from_to + pkt['addresse']
|
|
||||||
} else if (pkt.hasOwnProperty('to_call')) {
|
|
||||||
from_to = from_to + pkt['to_call']
|
|
||||||
} else if (pkt.hasOwnProperty('format') && pkt['format'] == 'mic-e') {
|
|
||||||
from_to = from_to + "Mic-E"
|
|
||||||
}
|
|
||||||
|
|
||||||
from_to = from_to + " - " + pkt['raw']
|
|
||||||
|
|
||||||
json_pretty = Prism.highlight(JSON.stringify(pkt, null, '\t'), Prism.languages.json, 'json');
|
|
||||||
pkt_html = '<div class="title" id="' + title_id + '"><i class="dropdown icon"></i>' + from_to + '</div><div class="content"><p class="transition hidden"><pre class="language-json">' + json_pretty + '</p></p></div>'
|
|
||||||
packetsdiv.prepend(pkt_html);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.ui.accordion').accordion('refresh');
|
|
||||||
|
|
||||||
// Update the count of messages shown
|
|
||||||
cnt = size_dict(packet_list);
|
|
||||||
//console.log("packets list " + cnt)
|
|
||||||
$('#packets_count').html(cnt);
|
|
||||||
|
|
||||||
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
|
||||||
$("#packetsjson").html(html_pretty);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function start_update() {
|
|
||||||
|
|
||||||
(function statsworker() {
|
|
||||||
$.ajax({
|
|
||||||
url: "/stats",
|
|
||||||
type: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
update_stats(data);
|
|
||||||
update_watchlist(data);
|
|
||||||
update_seenlist(data);
|
|
||||||
update_plugins(data);
|
|
||||||
update_threads(data);
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
setTimeout(statsworker, 10000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function packetsworker() {
|
|
||||||
$.ajax({
|
|
||||||
url: "/packets",
|
|
||||||
type: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
update_packets(data);
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
setTimeout(packetsworker, 10000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@ -1,114 +0,0 @@
|
|||||||
var cleared = false;
|
|
||||||
|
|
||||||
function size_dict(d){c=0; for (i in d) ++c; return c}
|
|
||||||
|
|
||||||
function init_messages() {
|
|
||||||
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 = $("#msgsDiv");
|
|
||||||
msgsdiv.html('')
|
|
||||||
cleared = true
|
|
||||||
}
|
|
||||||
add_msg(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("ack", function(msg) {
|
|
||||||
update_msg(msg);
|
|
||||||
});
|
|
||||||
socket.on("reply", function(msg) {
|
|
||||||
update_msg(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_msg(msg) {
|
|
||||||
var msgsdiv = $("#sendMsgsDiv");
|
|
||||||
|
|
||||||
ts_str = msg["ts"].toString();
|
|
||||||
ts = ts_str.split(".")[0]*1000;
|
|
||||||
var d = new Date(ts).toLocaleDateString("en-US")
|
|
||||||
var t = new Date(ts).toLocaleTimeString("en-US")
|
|
||||||
|
|
||||||
from = msg['from']
|
|
||||||
title_id = 'title_tx'
|
|
||||||
var from_to = d + " " + t + " " + from + " > "
|
|
||||||
|
|
||||||
if (msg.hasOwnProperty('to')) {
|
|
||||||
from_to = from_to + msg['to']
|
|
||||||
}
|
|
||||||
from_to = from_to + " - " + msg['message']
|
|
||||||
|
|
||||||
id = ts_str.split('.')[0]
|
|
||||||
pretty_id = "pretty_" + id
|
|
||||||
loader_id = "loader_" + id
|
|
||||||
ack_id = "ack_" + id
|
|
||||||
reply_id = "reply_" + id
|
|
||||||
span_id = "span_" + id
|
|
||||||
json_pretty = Prism.highlight(JSON.stringify(msg, null, '\t'), Prism.languages.json, 'json');
|
|
||||||
msg_html = '<div class="ui title" id="' + title_id + '"><i class="dropdown icon"></i>';
|
|
||||||
msg_html += '<div class="ui active inline loader" id="' + loader_id +'" data-content="Waiting for Ack"></div> ';
|
|
||||||
msg_html += '<i class="thumbs down outline icon" id="' + ack_id + '" data-content="Waiting for ACK"></i> ';
|
|
||||||
msg_html += '<i class="thumbs down outline icon" id="' + reply_id + '" data-content="Waiting for Reply"></i> ';
|
|
||||||
msg_html += '<span id="' + span_id + '">' + from_to +'</span></div>';
|
|
||||||
msg_html += '<div class="content"><p class="transition hidden"><pre id="' + pretty_id + '" class="language-json">' + json_pretty + '</p></p></div>'
|
|
||||||
msgsdiv.prepend(msg_html);
|
|
||||||
$('.ui.accordion').accordion('refresh');
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
reply_id = "reply_" + 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');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg['reply'] !== null) {
|
|
||||||
var reply_div = $('#' + reply_id);
|
|
||||||
reply_div.removeClass("thumbs down outline icon");
|
|
||||||
reply_div.addClass('reply icon');
|
|
||||||
reply_div.attr('data-content', 'Got Reply');
|
|
||||||
|
|
||||||
var d = new Date(ts).toLocaleDateString("en-US")
|
|
||||||
var t = new Date(ts).toLocaleTimeString("en-US")
|
|
||||||
var from_to = d + " " + t + " " + from + " > "
|
|
||||||
|
|
||||||
if (msg.hasOwnProperty('to')) {
|
|
||||||
from_to = from_to + msg['to']
|
|
||||||
}
|
|
||||||
from_to = from_to + " - " + msg['message']
|
|
||||||
from_to += " ===> " + msg["reply"]["message_text"]
|
|
||||||
|
|
||||||
var span_div = $('#' + span_id);
|
|
||||||
span_div.html(from_to);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pretty_pre = $("#" + pretty_id);
|
|
||||||
pretty_pre.html('');
|
|
||||||
json_pretty = Prism.highlight(JSON.stringify(msg, null, '\t'), Prism.languages.json, 'json');
|
|
||||||
pretty_pre.html(json_pretty);
|
|
||||||
$('.ui.accordion').accordion('refresh');
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
function openTab(evt, tabName) {
|
|
||||||
// Declare all variables
|
|
||||||
var i, tabcontent, tablinks;
|
|
||||||
|
|
||||||
if (typeof tabName == 'undefined') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all elements with class="tabcontent" and hide them
|
|
||||||
tabcontent = document.getElementsByClassName("tabcontent");
|
|
||||||
for (i = 0; i < tabcontent.length; i++) {
|
|
||||||
tabcontent[i].style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all elements with class="tablinks" and remove the class "active"
|
|
||||||
tablinks = document.getElementsByClassName("tablinks");
|
|
||||||
for (i = 0; i < tablinks.length; i++) {
|
|
||||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
|
||||||
document.getElementById(tabName).style.display = "block";
|
|
||||||
if (typeof evt.currentTarget == 'undefined') {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
evt.currentTarget.className += " active";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/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.7.1/socket.io.min.js" integrity="sha512-+NaO7d6gQ1YPxvc/qHIqZEchjGm207SszoNeMgppoqD/67fEqmc1edS8zrbxPD+4RQI3gDgT/83ihpFW61TG/Q==" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/index.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/tabs.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/prism.css">
|
|
||||||
<script src="/static/js/prism.js"></script>
|
|
||||||
<script src="/static/js/main.js"></script>
|
|
||||||
<script src="/static/js/echarts.js"></script>
|
|
||||||
<script src="/static/js/tabs.js"></script>
|
|
||||||
<script src="/static/js/send-message.js"></script>
|
|
||||||
<script src="/static/js/logs.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var initial_stats = {{ initial_stats|tojson|safe }};
|
|
||||||
|
|
||||||
var memory_chart = null
|
|
||||||
var message_chart = null
|
|
||||||
var color = Chart.helpers.color;
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
start_update();
|
|
||||||
start_charts();
|
|
||||||
init_messages();
|
|
||||||
init_logs();
|
|
||||||
|
|
||||||
$("#toggleStats").click(function() {
|
|
||||||
$("#jsonstats").fadeToggle(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pretty print the config json so it's readable
|
|
||||||
var cfg_data = $("#configjson").text();
|
|
||||||
var cfg_json = JSON.parse(cfg_data);
|
|
||||||
var cfg_pretty = JSON.stringify(cfg_json, null, '\t');
|
|
||||||
const html_pretty = Prism.highlight( cfg_pretty, Prism.languages.json, 'json');
|
|
||||||
$("#configjson").html(html_pretty);
|
|
||||||
$("#jsonstats").fadeToggle(1000);
|
|
||||||
|
|
||||||
//var log_text_pretty = $('#logtext').text();
|
|
||||||
//const log_pretty = Prism.highlight( log_text_pretty, Prism.languages.log, 'log');
|
|
||||||
//$('#logtext').html(log_pretty);
|
|
||||||
|
|
||||||
$('.ui.accordion').accordion({exclusive: false});
|
|
||||||
$('.menu .item').tab('change tab', 'charts-tab');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='ui text container'>
|
|
||||||
<h1 class='ui dividing header'>APRSD {{ version }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='ui grid text container'>
|
|
||||||
<div class='left floated ten 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>
|
|
||||||
|
|
||||||
<!-- Tab links -->
|
|
||||||
<div class="ui top attached tabular menu">
|
|
||||||
<div class="active item" data-tab="charts-tab">Charts</div>
|
|
||||||
<div class="item" data-tab="msgs-tab">Messages</div>
|
|
||||||
<div class="item" data-tab="seen-tab">Seen List</div>
|
|
||||||
<div class="item" data-tab="watch-tab">Watch List</div>
|
|
||||||
<div class="item" data-tab="plugin-tab">Plugins</div>
|
|
||||||
<div class="item" data-tab="threads-tab">Threads</div>
|
|
||||||
<div class="item" data-tab="config-tab">Config</div>
|
|
||||||
<div class="item" data-tab="log-tab">LogFile</div>
|
|
||||||
<!-- <div class="item" data-tab="oslo-tab">OSLO CONFIG</div> //-->
|
|
||||||
<div class="item" data-tab="raw-tab">Raw JSON</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tab content -->
|
|
||||||
<div class="ui bottom attached active tab segment" data-tab="charts-tab">
|
|
||||||
<h3 class="ui dividing header">Charts</h3>
|
|
||||||
<div class="ui equal width relaxed grid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui segment" style="height: 300px" id="packetsChart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui segment" style="height: 300px" id="messagesChart"></div>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui segment" style="height: 300px" id="acksChart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui segment" style="height: 300px" id="packetTypesChart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui segment" style="height: 300px" id="threadChart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui segment" style="height: 300px" id="memChart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="row">
|
|
||||||
<div id="stats" class="two column">
|
|
||||||
<button class="ui button" id="toggleStats">Toggle raw json</button>
|
|
||||||
<pre id="jsonstats" class="language-json">{{ stats }}</pre>
|
|
||||||
</div> //-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="msgs-tab">
|
|
||||||
<h3 class="ui dividing header">Messages (<span id="packets_count">0</span>)</h3>
|
|
||||||
<div class="ui styled fluid accordion" id="accordion">
|
|
||||||
<div id="packetsDiv" class="ui mini text">Loading</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="seen-tab">
|
|
||||||
<h3 class="ui dividing header">
|
|
||||||
Callsign Seen List (<span id="seen_count">{{ seen_count }}</span>)
|
|
||||||
</h3>
|
|
||||||
<div id="seenDiv" class="ui mini text">Loading</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="watch-tab">
|
|
||||||
<h3 class="ui dividing header">
|
|
||||||
Callsign Watch List (<span id="watch_count">{{ watch_count }}</span>)
|
|
||||||
|
|
||||||
Notification age - <span id="watch_age">{{ watch_age }}</span>
|
|
||||||
</h3>
|
|
||||||
<div id="watchDiv" class="ui mini text">Loading</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="plugin-tab">
|
|
||||||
<h3 class="ui dividing header">
|
|
||||||
Plugins Loaded (<span id="plugin_count">{{ plugin_count }}</span>)
|
|
||||||
</h3>
|
|
||||||
<div id="pluginDiv" class="ui mini text">Loading</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="threads-tab">
|
|
||||||
<h3 class="ui dividing header">
|
|
||||||
Threads Loaded (<span id="thread_count">{{ thread_count }}</span>)
|
|
||||||
</h3>
|
|
||||||
<div id="threadsDiv" class="ui mini text">Loading</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="config-tab">
|
|
||||||
<h3 class="ui dividing header">Config</h3>
|
|
||||||
<pre id="configjson" class="language-json">{{ config_json|safe }}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="log-tab">
|
|
||||||
<h3 class="ui dividing header">LOGFILE</h3>
|
|
||||||
<pre id="logContainer" style="height: 600px;overflow-y:auto;overflow-x:auto;"><code id="logtext" class="language-log" ></code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="oslo-tab">
|
|
||||||
<h3 class="ui dividing header">OSLO</h3>
|
|
||||||
<pre id="osloContainer" style="height:600px;overflow-y:auto;" class="language-json">{{ oslo_out|safe }}</pre>
|
|
||||||
</div> //-->
|
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="raw-tab">
|
|
||||||
<h3 class="ui dividing header">Raw JSON</h3>
|
|
||||||
<pre id="jsonstats" class="language-yaml" style="height:600px;overflow-y:auto;">{{ initial_stats|safe }}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui text container">
|
|
||||||
<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>
|
|
@ -1,115 +0,0 @@
|
|||||||
input[type=search]::-webkit-search-cancel-button {
|
|
||||||
-webkit-appearance: searchfield-cancel-button;
|
|
||||||
}
|
|
||||||
|
|
||||||
.speech-wrapper {
|
|
||||||
padding-top: 0px;
|
|
||||||
padding: 5px 30px;
|
|
||||||
background-color: #CCCCCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-row {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-row.alt {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble {
|
|
||||||
/*width: 350px; */
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 2px 8px 5px #555;
|
|
||||||
position: relative;
|
|
||||||
margin: 0 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble.alt {
|
|
||||||
margin: 0 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-text {
|
|
||||||
padding: 5px 5px 0px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-name {
|
|
||||||
width: 280px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 0 0 0px;
|
|
||||||
color: #3498db;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.material-symbols-rounded {
|
|
||||||
margin-left: auto;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #808080;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bubble-name.alt {
|
|
||||||
color: #2ecc71;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-timestamp {
|
|
||||||
margin-right: auto;
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #bbb
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-message {
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px 0px 0px 0px;
|
|
||||||
color: #2b2b2b;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-arrow {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
bottom:30px;
|
|
||||||
left: -16px;
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-arrow.alt {
|
|
||||||
right: -2px;
|
|
||||||
bottom: 30px;
|
|
||||||
left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-arrow:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
border: 0 solid transparent;
|
|
||||||
border-top: 9px solid #f5f5f5;
|
|
||||||
border-radius: 0 20px 0;
|
|
||||||
width: 15px;
|
|
||||||
height: 30px;
|
|
||||||
transform: rotate(145deg);
|
|
||||||
}
|
|
||||||
.bubble-arrow.alt:after {
|
|
||||||
transform: rotate(45deg) scaleY(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover {
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
.popover-header {
|
|
||||||
font-size: 8pt;
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-body {
|
|
||||||
white-space: pre-line;
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
body {
|
|
||||||
background: #eeeeee;
|
|
||||||
/*margin: 1em;*/
|
|
||||||
text-align: center;
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title {
|
|
||||||
font-size: 4em;
|
|
||||||
}
|
|
||||||
#version{
|
|
||||||
font-size: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#uptime, #aprsis {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
#callsign {
|
|
||||||
font-size: 1.4em;
|
|
||||||
color: #00F;
|
|
||||||
padding-top: 8px;
|
|
||||||
margin:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title_rx {
|
|
||||||
background-color: darkseagreen;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title_tx {
|
|
||||||
background-color: lightcoral;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aprsd_1 {
|
|
||||||
background-image: url(/static/images/aprs-symbols-16-0.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: -160px -48px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wc-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.wc-container .wc-row {
|
|
||||||
/*border: 1px dotted #0313fc;*/
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
.wc-container .wc-row.header {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
.wc-container .wc-row.content {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.wc-container .wc-row.footer {
|
|
||||||
flex: 0 1 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-symbols-rounded.md-10 {
|
|
||||||
font-size: 18px !important;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@ -1,41 +0,0 @@
|
|||||||
* {box-sizing: border-box}
|
|
||||||
|
|
||||||
/* Style the tab */
|
|
||||||
.tab {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
height: 450px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style the buttons inside the tab */
|
|
||||||
.tab div {
|
|
||||||
display: block;
|
|
||||||
background-color: inherit;
|
|
||||||
color: black;
|
|
||||||
padding: 10px;
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.3s;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Change background color of buttons on hover */
|
|
||||||
.tab div:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create an active/current "tab button" class */
|
|
||||||
.tab div.active {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style the tab content */
|
|
||||||
.tabcontent {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
height: 450px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
background-color: #CCCCCC;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,23 +0,0 @@
|
|||||||
/* fallback */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Material Symbols Rounded';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 200;
|
|
||||||
src: url(/static/css/upstream/font.woff2) format('woff2');
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-symbols-rounded {
|
|
||||||
font-family: 'Material Symbols Rounded';
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: normal;
|
|
||||||
text-transform: none;
|
|
||||||
display: inline-block;
|
|
||||||
white-space: nowrap;
|
|
||||||
word-wrap: normal;
|
|
||||||
direction: ltr;
|
|
||||||
-webkit-font-feature-settings: 'liga';
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
1311
aprsd/web/chat/static/css/upstream/jquery-ui.css
vendored
1311
aprsd/web/chat/static/css/upstream/jquery-ui.css
vendored
File diff suppressed because it is too large
Load Diff
@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* jQuery toast plugin created by Kamran Ahmed copyright MIT license 2014
|
|
||||||
*/
|
|
||||||
.jq-toast-wrap { display: block; position: fixed; width: 250px; pointer-events: none !important; margin: 0; padding: 0; letter-spacing: normal; z-index: 9000 !important; }
|
|
||||||
.jq-toast-wrap * { margin: 0; padding: 0; }
|
|
||||||
|
|
||||||
.jq-toast-wrap.bottom-left { bottom: 20px; left: 20px; }
|
|
||||||
.jq-toast-wrap.bottom-right { bottom: 20px; right: 40px; }
|
|
||||||
.jq-toast-wrap.top-left { top: 20px; left: 20px; }
|
|
||||||
.jq-toast-wrap.top-right { top: 20px; right: 40px; }
|
|
||||||
|
|
||||||
.jq-toast-single { display: block; width: 100%; padding: 10px; margin: 0px 0px 5px; border-radius: 4px; font-size: 12px; font-family: arial, sans-serif; line-height: 17px; position: relative; pointer-events: all !important; background-color: #444444; color: white; }
|
|
||||||
|
|
||||||
.jq-toast-single h2 { font-family: arial, sans-serif; font-size: 14px; margin: 0px 0px 7px; background: none; color: inherit; line-height: inherit; letter-spacing: normal; }
|
|
||||||
.jq-toast-single a { color: #eee; text-decoration: none; font-weight: bold; border-bottom: 1px solid white; padding-bottom: 3px; font-size: 12px; }
|
|
||||||
|
|
||||||
.jq-toast-single ul { margin: 0px 0px 0px 15px; background: none; padding:0px; }
|
|
||||||
.jq-toast-single ul li { list-style-type: disc !important; line-height: 17px; background: none; margin: 0; padding: 0; letter-spacing: normal; }
|
|
||||||
|
|
||||||
.close-jq-toast-single { position: absolute; top: 3px; right: 7px; font-size: 14px; cursor: pointer; }
|
|
||||||
|
|
||||||
.jq-toast-loader { display: block; position: absolute; top: -2px; height: 5px; width: 0%; left: 0; border-radius: 5px; background: red; }
|
|
||||||
.jq-toast-loaded { width: 100%; }
|
|
||||||
.jq-has-icon { padding: 10px 10px 10px 50px; background-repeat: no-repeat; background-position: 10px; }
|
|
||||||
.jq-icon-info { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII='); background-color: #31708f; color: #d9edf7; border-color: #bce8f1; }
|
|
||||||
.jq-icon-warning { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII='); background-color: #8a6d3b; color: #fcf8e3; border-color: #faebcc; }
|
|
||||||
.jq-icon-error { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII='); background-color: #a94442; color: #f2dede; border-color: #ebccd1; }
|
|
||||||
.jq-icon-success { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg=='); color: #dff0d8; background-color: #3c763d; border-color: #d6e9c6; }
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before ![]() (image error) Size: 37 KiB |
Binary file not shown.
Before ![]() (image error) Size: 52 KiB |
Binary file not shown.
Before ![]() (image error) Size: 48 KiB |
Binary file not shown.
Before ![]() (image error) Size: 52 KiB |
Binary file not shown.
Before ![]() (image error) Size: 48 KiB |
Binary file not shown.
Before ![]() (image error) Size: 40 KiB |
@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-globe" viewBox="0 0 16 16">
|
|
||||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m7.5-6.923c-.67.204-1.335.82-1.887 1.855A8 8 0 0 0 5.145 4H7.5zM4.09 4a9.3 9.3 0 0 1 .64-1.539 7 7 0 0 1 .597-.933A7.03 7.03 0 0 0 2.255 4zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a7 7 0 0 0-.656 2.5zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5zM8.5 5v2.5h2.99a12.5 12.5 0 0 0-.337-2.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5zM5.145 12q.208.58.468 1.068c.552 1.035 1.218 1.65 1.887 1.855V12zm.182 2.472a7 7 0 0 1-.597-.933A9.3 9.3 0 0 1 4.09 12H2.255a7 7 0 0 0 3.072 2.472M3.82 11a13.7 13.7 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5zm6.853 3.472A7 7 0 0 0 13.745 12H11.91a9.3 9.3 0 0 1-.64 1.539 7 7 0 0 1-.597.933M8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855q.26-.487.468-1.068zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.7 13.7 0 0 1-.312 2.5m2.802-3.5a7 7 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7 7 0 0 0-3.072-2.472c.218.284.418.598.597.933M10.855 4a8 8 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4z"/>
|
|
||||||
</svg>
|
|
Before (image error) Size: 1.2 KiB |
@ -1,84 +0,0 @@
|
|||||||
|
|
||||||
function init_gps() {
|
|
||||||
console.log("init_gps Called.")
|
|
||||||
console.log("latitude: "+latitude)
|
|
||||||
console.log("longitude: "+longitude)
|
|
||||||
$("#send_beacon").click(function() {
|
|
||||||
console.log("Send a beacon!")
|
|
||||||
if (!isNaN(latitude) && !isNaN(longitude)) {
|
|
||||||
// webchat admin has hard coded lat/long in the config file
|
|
||||||
showPosition({'coords': {'latitude': latitude, 'longitude': longitude}})
|
|
||||||
} else {
|
|
||||||
// Try to get the current location from the browser
|
|
||||||
getLocation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocation() {
|
|
||||||
if (navigator.geolocation) {
|
|
||||||
console.log("getCurrentPosition");
|
|
||||||
try {
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
showPosition, showError,
|
|
||||||
{timeout:3000});
|
|
||||||
} catch(err) {
|
|
||||||
console.log("Failed to getCurrentPosition");
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
} 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 location fix timed out."
|
|
||||||
break;
|
|
||||||
case error.UNKNOWN_ERROR:
|
|
||||||
msg = "An unknown error occurred."
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log(msg);
|
|
||||||
$.toast({
|
|
||||||
title: 'GPS Error',
|
|
||||||
class: 'warning',
|
|
||||||
position: 'middle center',
|
|
||||||
message: msg,
|
|
||||||
showProgress: 'top',
|
|
||||||
classProgress: 'blue',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPosition(position) {
|
|
||||||
console.log("showPosition Called");
|
|
||||||
path = $('#pkt_path option:selected').val();
|
|
||||||
msg = {
|
|
||||||
'latitude': position.coords.latitude,
|
|
||||||
'longitude': position.coords.longitude,
|
|
||||||
'path': path,
|
|
||||||
}
|
|
||||||
console.log(msg);
|
|
||||||
$.toast({
|
|
||||||
heading: 'Sending GPS Beacon',
|
|
||||||
text: "Latitude: "+position.coords.latitude+"<br>Longitude: "+position.coords.longitude,
|
|
||||||
loader: true,
|
|
||||||
loaderBg: '#9EC600',
|
|
||||||
position: 'top-center',
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Sending GPS msg")
|
|
||||||
socket.emit("gps", msg);
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
function aprs_img(item, x_offset, y_offset) {
|
|
||||||
var x = x_offset * -16;
|
|
||||||
if (y_offset > 5) {
|
|
||||||
y_offset = 5;
|
|
||||||
}
|
|
||||||
var y = y_offset * -16;
|
|
||||||
var loc = x + 'px '+ y + 'px'
|
|
||||||
item.css('background-position', loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
function show_aprs_icon(item, symbol) {
|
|
||||||
var offset = ord(symbol) - 33;
|
|
||||||
var col = Math.floor(offset / 16);
|
|
||||||
var row = offset % 16;
|
|
||||||
//console.log("'" + symbol+"' off: "+offset+" row: "+ row + " col: " + col)
|
|
||||||
aprs_img(item, row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ord(str){return str.charCodeAt(0);}
|
|
||||||
|
|
||||||
function update_stats( data ) {
|
|
||||||
console.log(data);
|
|
||||||
$("#version").text( data["stats"]["APRSDStats"]["version"] );
|
|
||||||
$("#aprs_connection").html( data["aprs_connection"] );
|
|
||||||
$("#uptime").text( "uptime: " + data["stats"]["APRSDStats"]["uptime"] );
|
|
||||||
short_time = data["time"].split(/\s(.+)/)[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function start_update() {
|
|
||||||
|
|
||||||
(function statsworker() {
|
|
||||||
$.ajax({
|
|
||||||
url: "/stats",
|
|
||||||
type: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
update_stats(data);
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
setTimeout(statsworker, 60000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
}
|
|
@ -1,612 +0,0 @@
|
|||||||
var cleared = false;
|
|
||||||
var callsign_list = {};
|
|
||||||
var callsign_location = {};
|
|
||||||
var message_list = {};
|
|
||||||
var from_msg_list = {};
|
|
||||||
var selected_tab_callsign = null;
|
|
||||||
const socket = io("/sendmsg");
|
|
||||||
|
|
||||||
MSG_TYPE_TX = "tx";
|
|
||||||
MSG_TYPE_RX = "rx";
|
|
||||||
MSG_TYPE_ACK = "ack";
|
|
||||||
|
|
||||||
function reload_popovers() {
|
|
||||||
$('[data-bs-toggle="popover"]').popover(
|
|
||||||
{html: true, animation: true}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function build_location_string(msg) {
|
|
||||||
dt = new Date(parseInt(msg['lasttime']) * 1000);
|
|
||||||
loc = "Last Location Update: " + dt.toLocaleString();
|
|
||||||
loc += "<br>Latitude: " + msg['lat'] + "<br>Longitude: " + msg['lon'];
|
|
||||||
loc += "<br>" + "Altitude: " + msg['altitude'] + " m";
|
|
||||||
loc += "<br>" + "Speed: " + msg['speed'] + " kph";
|
|
||||||
loc += "<br>" + "Bearing: " + msg['compass_bearing'];
|
|
||||||
loc += "<br>" + "distance: " + msg['distance'] + " km";
|
|
||||||
return loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
function build_location_string_small(msg) {
|
|
||||||
dt = new Date(parseInt(msg['lasttime']) * 1000);
|
|
||||||
loc = "" + msg['distance'] + "km";
|
|
||||||
//loc += "Lat " + msg['lat'] + " Lon " + msg['lon'];
|
|
||||||
loc += " " + msg['compass_bearing'];
|
|
||||||
//loc += " Distance " + msg['distance'] + " km";
|
|
||||||
//loc += " " + dt.toLocaleString();
|
|
||||||
loc += " " + msg['timeago'];
|
|
||||||
return loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
function size_dict(d){c=0; for (i in d) ++c; return c}
|
|
||||||
|
|
||||||
function raise_error(msg) {
|
|
||||||
$.toast({
|
|
||||||
heading: 'Error',
|
|
||||||
text: msg,
|
|
||||||
loader: true,
|
|
||||||
loaderBg: '#9EC600',
|
|
||||||
position: 'top-center',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function init_chat() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
msg["type"] = MSG_TYPE_TX;
|
|
||||||
sent_msg(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("ack", function(msg) {
|
|
||||||
msg["type"] = MSG_TYPE_ACK;
|
|
||||||
ack_msg(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("new", function(msg) {
|
|
||||||
if (cleared === false) {
|
|
||||||
var msgsdiv = $("#msgsTabsDiv");
|
|
||||||
msgsdiv.html('')
|
|
||||||
cleared = true;
|
|
||||||
}
|
|
||||||
msg["type"] = MSG_TYPE_RX;
|
|
||||||
from_msg(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("callsign_location", function(msg) {
|
|
||||||
console.log("CALLSIGN Location!");
|
|
||||||
console.log(msg);
|
|
||||||
now = new Date();
|
|
||||||
msg['last_updated'] = now;
|
|
||||||
callsign_location[msg['callsign']] = msg;
|
|
||||||
|
|
||||||
location_id = callsign_location_content(msg['callsign'], true);
|
|
||||||
location_string = build_location_string_small(msg);
|
|
||||||
$(location_id).html(location_string);
|
|
||||||
$(location_id+"Spinner").addClass('d-none');
|
|
||||||
save_data();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#sendform").submit(function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
to_call = $('#to_call').val().toUpperCase();
|
|
||||||
message = $('#message').val();
|
|
||||||
path = $('#pkt_path option:selected').val();
|
|
||||||
if (to_call == "") {
|
|
||||||
raise_error("You must enter a callsign to send a message")
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if (message == "") {
|
|
||||||
raise_error("You must enter a message to send")
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
msg = {'to': to_call, 'message': message, 'path': path};
|
|
||||||
//console.log(msg);
|
|
||||||
socket.emit("send", msg);
|
|
||||||
$('#message').val('');
|
|
||||||
callsign_select(to_call);
|
|
||||||
activate_callsign_tab(to_call);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
init_gps();
|
|
||||||
// Try and load any existing chat threads from last time
|
|
||||||
init_messages();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function tab_string(callsign, id=false) {
|
|
||||||
name = "msgs"+callsign;
|
|
||||||
if (id) {
|
|
||||||
return "#"+name;
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab_li_string(callsign, id=false) {
|
|
||||||
//The id of the LI containing the tab
|
|
||||||
return tab_string(callsign,id)+"Li";
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab_notification_id(callsign, id=false) {
|
|
||||||
// The ID of the span that contains the notification count
|
|
||||||
return tab_string(callsign, id)+"notify";
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab_content_name(callsign, id=false) {
|
|
||||||
return tab_string(callsign, id)+"Content";
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab_content_speech_wrapper(callsign, id=false) {
|
|
||||||
return tab_string(callsign, id)+"SpeechWrapper";
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab_content_speech_wrapper_id(callsign) {
|
|
||||||
return "#"+tab_content_speech_wrapper(callsign);
|
|
||||||
}
|
|
||||||
|
|
||||||
function content_divname(callsign) {
|
|
||||||
return "#"+tab_content_name(callsign);
|
|
||||||
}
|
|
||||||
|
|
||||||
function callsign_tab(callsign) {
|
|
||||||
return "#"+tab_string(callsign);
|
|
||||||
}
|
|
||||||
|
|
||||||
function callsign_location_popover(callsign, id=false) {
|
|
||||||
return tab_string(callsign, id)+"Location";
|
|
||||||
}
|
|
||||||
|
|
||||||
function callsign_location_content(callsign, id=false) {
|
|
||||||
return tab_string(callsign, id)+"LocationContent";
|
|
||||||
}
|
|
||||||
|
|
||||||
function bubble_msg_id(msg, id=false) {
|
|
||||||
// The id of the div that contains a specific message
|
|
||||||
name = msg["from_call"] + "_" + msg["msgNo"];
|
|
||||||
if (id) {
|
|
||||||
return "#"+name;
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function message_ts_id(msg) {
|
|
||||||
//Create a 'id' from the message timestamp
|
|
||||||
ts_str = msg["timestamp"].toString();
|
|
||||||
ts = ts_str.split(".")[0]*1000;
|
|
||||||
id = ts_str.split('.')[0];
|
|
||||||
return {'timestamp': ts, 'id': id};
|
|
||||||
}
|
|
||||||
|
|
||||||
function time_ack_from_msg(msg) {
|
|
||||||
// Return the time and ack_id from a message
|
|
||||||
ts_id = message_ts_id(msg);
|
|
||||||
ts = ts_id['timestamp'];
|
|
||||||
id = ts_id['id'];
|
|
||||||
ack_id = "ack_" + id
|
|
||||||
|
|
||||||
var d = new Date(ts).toLocaleDateString("en-US")
|
|
||||||
var t = new Date(ts).toLocaleTimeString("en-US")
|
|
||||||
return {'time': t, 'date': d, 'ack_id': ack_id};
|
|
||||||
}
|
|
||||||
|
|
||||||
function save_data() {
|
|
||||||
// Save the relevant data to local storage
|
|
||||||
localStorage.setItem('callsign_list', JSON.stringify(callsign_list));
|
|
||||||
localStorage.setItem('message_list', JSON.stringify(message_list));
|
|
||||||
localStorage.setItem('callsign_location', JSON.stringify(callsign_location));
|
|
||||||
}
|
|
||||||
|
|
||||||
function init_messages() {
|
|
||||||
// This tries to load any previous conversations from local storage
|
|
||||||
callsign_list = JSON.parse(localStorage.getItem('callsign_list'));
|
|
||||||
message_list = JSON.parse(localStorage.getItem('message_list'));
|
|
||||||
callsign_location = JSON.parse(localStorage.getItem('callsign_location'));
|
|
||||||
if (callsign_list == null) {
|
|
||||||
callsign_list = {};
|
|
||||||
}
|
|
||||||
if (message_list == null) {
|
|
||||||
message_list = {};
|
|
||||||
}
|
|
||||||
if (callsign_location == null) {
|
|
||||||
callsign_location = {};
|
|
||||||
}
|
|
||||||
console.log(callsign_list);
|
|
||||||
console.log(message_list);
|
|
||||||
console.log(callsign_location);
|
|
||||||
|
|
||||||
// Now loop through each callsign and add the tabs
|
|
||||||
first_callsign = null;
|
|
||||||
for (callsign in callsign_list) {
|
|
||||||
if (first_callsign === null) {
|
|
||||||
first_callsign = callsign;
|
|
||||||
active = true;
|
|
||||||
} else {
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
create_callsign_tab(callsign, active);
|
|
||||||
}
|
|
||||||
// and then populate the messages in order
|
|
||||||
for (callsign in message_list) {
|
|
||||||
new_callsign = true;
|
|
||||||
cleared = true;
|
|
||||||
for (id in message_list[callsign]) {
|
|
||||||
msg = message_list[callsign][id];
|
|
||||||
info = time_ack_from_msg(msg);
|
|
||||||
t = info['time'];
|
|
||||||
d = info['date'];
|
|
||||||
ack_id = false;
|
|
||||||
acked = false;
|
|
||||||
if (msg['type'] == MSG_TYPE_TX) {
|
|
||||||
ack_id = info['ack_id'];
|
|
||||||
acked = msg['ack'];
|
|
||||||
}
|
|
||||||
msg_html = create_message_html(d, t, msg['from_call'], msg['to_call'],
|
|
||||||
msg['message_text'], ack_id, msg, acked);
|
|
||||||
append_message_html(callsign, msg_html, new_callsign);
|
|
||||||
new_callsign = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first_callsign !== null) {
|
|
||||||
callsign_select(first_callsign);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function scroll_main_content(callsign=false) {
|
|
||||||
var wc = $('#wc-content');
|
|
||||||
var d = $('#msgsTabContent');
|
|
||||||
var scrollHeight = wc.prop('scrollHeight');
|
|
||||||
var clientHeight = wc.prop('clientHeight');
|
|
||||||
|
|
||||||
if (callsign) {
|
|
||||||
div_id = content_divname(callsign);
|
|
||||||
c_div = $(content_divname(callsign));
|
|
||||||
//console.log("c_div("+div_id+") " + c_div);
|
|
||||||
c_height = c_div.height();
|
|
||||||
c_scroll_height = c_div.prop('scrollHeight');
|
|
||||||
//console.log("callsign height " + c_height + " scrollHeight " + c_scroll_height);
|
|
||||||
if (c_height === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (c_height > clientHeight) {
|
|
||||||
wc.animate({ scrollTop: c_scroll_height }, 500);
|
|
||||||
} else {
|
|
||||||
wc.animate({ scrollTop: 0 }, 500);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (scrollHeight > clientHeight) {
|
|
||||||
wc.animate({ scrollTop: wc.prop('scrollHeight') }, 500);
|
|
||||||
} else {
|
|
||||||
wc.animate({ scrollTop: 0 }, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_callsign_tab(callsign, active=false) {
|
|
||||||
//Create the html for the callsign tab and insert it into the DOM
|
|
||||||
var callsignTabs = $("#msgsTabList");
|
|
||||||
tab_id = tab_string(callsign);
|
|
||||||
tab_id_li = tab_li_string(callsign);
|
|
||||||
tab_notify_id = tab_notification_id(callsign);
|
|
||||||
tab_content = tab_content_name(callsign);
|
|
||||||
popover_id = callsign_location_popover(callsign);
|
|
||||||
if (active) {
|
|
||||||
active_str = "active";
|
|
||||||
} else {
|
|
||||||
active_str = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
item_html = '<li class="nav-item" role="presentation" callsign="'+callsign+'" id="'+tab_id_li+'">';
|
|
||||||
//item_html += '<button onClick="callsign_select(\''+callsign+'\');" callsign="'+callsign+'" class="nav-link '+active_str+'" id="'+tab_id+'" data-bs-toggle="tab" data-bs-target="#'+tab_content+'" type="button" role="tab" aria-controls="'+callsign+'" aria-selected="true">';
|
|
||||||
item_html += '<button onClick="callsign_select(\''+callsign+'\');" callsign="'+callsign+'" class="nav-link position-relative '+active_str+'" id="'+tab_id+'" data-bs-toggle="tab" data-bs-target="#'+tab_content+'" type="button" role="tab" aria-controls="'+callsign+'" aria-selected="true">';
|
|
||||||
item_html += callsign+' ';
|
|
||||||
item_html += '<span id="'+tab_notify_id+'" class="position-absolute top-0 start-80 translate-middle badge bg-danger border border-light rounded-pill visually-hidden">0</span>';
|
|
||||||
item_html += '<span onclick="delete_tab(\''+callsign+'\');">×</span>';
|
|
||||||
item_html += '</button></li>'
|
|
||||||
|
|
||||||
callsignTabs.append(item_html);
|
|
||||||
create_callsign_tab_content(callsign, active);
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_callsign_tab_content(callsign, active=false) {
|
|
||||||
var callsignTabsContent = $("#msgsTabContent");
|
|
||||||
tab_id = tab_string(callsign);
|
|
||||||
tab_content = tab_content_name(callsign);
|
|
||||||
wrapper_id = tab_content_speech_wrapper(callsign);
|
|
||||||
if (active) {
|
|
||||||
active_str = "show active";
|
|
||||||
} else {
|
|
||||||
active_str = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
location_str = "Unknown Location"
|
|
||||||
if (callsign in callsign_location) {
|
|
||||||
location_str = build_location_string_small(callsign_location[callsign]);
|
|
||||||
location_class = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
location_id = callsign_location_content(callsign);
|
|
||||||
|
|
||||||
item_html = '<div class="tab-pane fade '+active_str+'" id="'+tab_content+'" role="tabpanel" aria-labelledby="'+tab_id+'">';
|
|
||||||
item_html += '<div class="" style="border: 1px solid #999999;background-color:#aaaaaa;">';
|
|
||||||
item_html += '<div class="row" style="padding-top:4px;padding-bottom:4px;background-color:#aaaaaa;margin:0px;">';
|
|
||||||
item_html += '<div class="d-flex col-md-10 justify-content-left" style="padding:0px;margin:0px;">';
|
|
||||||
item_html += '<button onclick="call_callsign_location(\''+callsign+'\');" style="margin-left:2px;padding: 0px 4px 0px 4px;font-size: .9rem" type="button" class="btn btn-primary">';
|
|
||||||
item_html += '<span id="'+location_id+'Spinner" class="d-none spinner-border spinner-border-sm" role="status" aria-hidden="true" style="font-size: .9rem"></span>Update</button>';
|
|
||||||
item_html += ' <span id="'+location_id+'" style="font-size: .9rem">'+location_str+'</span></div>';
|
|
||||||
item_html += '</div>';
|
|
||||||
item_html += '<div class="speech-wrapper" id="'+wrapper_id+'"></div>';
|
|
||||||
item_html += '</div>';
|
|
||||||
callsignTabsContent.append(item_html);
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_tab(callsign) {
|
|
||||||
// User asked to delete the tab and the conversation
|
|
||||||
tab_id = tab_string(callsign, true);
|
|
||||||
tab_id_li = tab_li_string(callsign, true);
|
|
||||||
tab_content = tab_content_name(callsign, true);
|
|
||||||
$(tab_id_li).remove();
|
|
||||||
$(tab_content).remove();
|
|
||||||
delete callsign_list[callsign];
|
|
||||||
delete message_list[callsign];
|
|
||||||
delete callsign_location[callsign];
|
|
||||||
|
|
||||||
// Now select the first tab
|
|
||||||
first_tab = $("#msgsTabList").children().first().children().first();
|
|
||||||
console.log(first_tab);
|
|
||||||
$(first_tab).click();
|
|
||||||
save_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_callsign(callsign, msg) {
|
|
||||||
/* Ensure a callsign exists in the left hand nav */
|
|
||||||
if (callsign in callsign_list) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
len = Object.keys(callsign_list).length;
|
|
||||||
if (len == 0) {
|
|
||||||
active = true;
|
|
||||||
} else {
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
create_callsign_tab(callsign, active);
|
|
||||||
callsign_list[callsign] = '';
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_callsign_path(callsign, msg) {
|
|
||||||
//Get the selected path to save for this callsign
|
|
||||||
path = msg['path']
|
|
||||||
$('#pkt_path').val(path);
|
|
||||||
callsign_list[callsign] = path;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function append_message(callsign, msg, msg_html) {
|
|
||||||
new_callsign = false
|
|
||||||
if (!message_list.hasOwnProperty(callsign)) {
|
|
||||||
message_list[callsign] = {};
|
|
||||||
}
|
|
||||||
ts_id = message_ts_id(msg);
|
|
||||||
id = ts_id['id']
|
|
||||||
message_list[callsign][id] = msg;
|
|
||||||
if (selected_tab_callsign != callsign) {
|
|
||||||
// We need to update the notification for the tab
|
|
||||||
tab_notify_id = tab_notification_id(callsign, true);
|
|
||||||
// get the current count of notifications
|
|
||||||
count = parseInt($(tab_notify_id).text());
|
|
||||||
if (isNaN(count)) {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
$(tab_notify_id).text(count);
|
|
||||||
$(tab_notify_id).removeClass('visually-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the right div to place the html
|
|
||||||
new_callsign = add_callsign(callsign, msg);
|
|
||||||
//update_callsign_path(callsign, msg);
|
|
||||||
append_message_html(callsign, msg_html, new_callsign);
|
|
||||||
len = Object.keys(callsign_list).length;
|
|
||||||
if (new_callsign) {
|
|
||||||
//Now click the tab if and only if there is only one tab
|
|
||||||
callsign_tab_id = callsign_tab(callsign);
|
|
||||||
$(callsign_tab_id).click();
|
|
||||||
callsign_select(callsign);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function append_message_html(callsign, msg_html, new_callsign) {
|
|
||||||
var msgsTabs = $('#msgsTabsDiv');
|
|
||||||
divname_str = tab_content_name(callsign);
|
|
||||||
divname = content_divname(callsign);
|
|
||||||
tab_content = tab_content_name(callsign);
|
|
||||||
wrapper_id = tab_content_speech_wrapper_id(callsign);
|
|
||||||
|
|
||||||
$(wrapper_id).append(msg_html);
|
|
||||||
|
|
||||||
if ($(wrapper_id).children().length > 0) {
|
|
||||||
$(wrapper_id).animate({scrollTop: $(wrapper_id)[0].scrollHeight}, "fast");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_message_html(date, time, from, to, message, ack_id, msg, acked=false) {
|
|
||||||
div_id = from + "_" + msg.msgNo;
|
|
||||||
if (ack_id) {
|
|
||||||
alt = " alt"
|
|
||||||
} else {
|
|
||||||
alt = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
bubble_class = "bubble" + alt + " text-nowrap"
|
|
||||||
bubble_name_class = "bubble-name" + alt
|
|
||||||
bubble_msgid = bubble_msg_id(msg);
|
|
||||||
date_str = date + " " + time;
|
|
||||||
sane_date_str = date_str.replace(/ /g,"").replaceAll("/","").replaceAll(":","");
|
|
||||||
|
|
||||||
bubble_msg_class = "bubble-message";
|
|
||||||
if (ack_id) {
|
|
||||||
bubble_arrow_class = "bubble-arrow alt";
|
|
||||||
popover_placement = "left";
|
|
||||||
} else {
|
|
||||||
bubble_arrow_class = "bubble-arrow";
|
|
||||||
popover_placement = "right";
|
|
||||||
}
|
|
||||||
|
|
||||||
msg_html = '<div class="bubble-row'+alt+'">';
|
|
||||||
msg_html += '<div id="'+bubble_msgid+'" class="'+ bubble_class + '" ';
|
|
||||||
msg_html += 'title="APRS Raw Packet" data-bs-placement="'+popover_placement+'" data-bs-toggle="popover" ';
|
|
||||||
msg_html += 'data-bs-trigger="hover" data-bs-content="'+msg['raw']+'">';
|
|
||||||
msg_html += '<div class="bubble-text">';
|
|
||||||
msg_html += '<p class="'+ bubble_name_class +'">'+from+' ';
|
|
||||||
msg_html += '<span class="bubble-timestamp">'+date_str+'</span>';
|
|
||||||
|
|
||||||
if (ack_id) {
|
|
||||||
if (acked) {
|
|
||||||
msg_html += '<span class="material-symbols-rounded md-10" id="' + ack_id + '">thumb_up</span>';
|
|
||||||
} else {
|
|
||||||
msg_html += '<span class="material-symbols-rounded md-10" id="' + ack_id + '">thumb_down</span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg_html += "</p>";
|
|
||||||
msg_html += '<p class="' +bubble_msg_class+ '">'+message+'</p>';
|
|
||||||
msg_html += '<div class="'+ bubble_arrow_class + '"></div>';
|
|
||||||
msg_html += "</div></div></div>";
|
|
||||||
|
|
||||||
return msg_html
|
|
||||||
}
|
|
||||||
|
|
||||||
function flash_message(msg) {
|
|
||||||
// Callback function to bring a hidden box back
|
|
||||||
msg_id = bubble_msg_id(msg, true);
|
|
||||||
$(msg_id).fadeOut(100).fadeIn(100).fadeOut(100).fadeIn(100).fadeOut(100).fadeIn(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sent_msg(msg) {
|
|
||||||
info = time_ack_from_msg(msg);
|
|
||||||
t = info['time'];
|
|
||||||
d = info['date'];
|
|
||||||
ack_id = info['ack_id'];
|
|
||||||
|
|
||||||
msg_html = create_message_html(d, t, msg['from_call'], msg['to_call'], msg['message_text'], ack_id, msg, false);
|
|
||||||
append_message(msg['to_call'], msg, msg_html);
|
|
||||||
save_data();
|
|
||||||
scroll_main_content(msg['to_call']);
|
|
||||||
reload_popovers();
|
|
||||||
}
|
|
||||||
|
|
||||||
function str_to_int(my_string) {
|
|
||||||
total = 0
|
|
||||||
for (let i = 0; i < my_string.length; i++) {
|
|
||||||
total += my_string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
function from_msg(msg) {
|
|
||||||
if (!from_msg_list.hasOwnProperty(msg["from_call"])) {
|
|
||||||
from_msg_list[msg["from_call"]] = new Array();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to account for messages that have no msgNo
|
|
||||||
console.log(msg)
|
|
||||||
if (msg["msgNo"] == null) {
|
|
||||||
console.log("Need to add msgNO!!")
|
|
||||||
// create an artificial msgNo
|
|
||||||
total = str_to_int(msg["from_call"])
|
|
||||||
total += str_to_int(msg["addresse"])
|
|
||||||
total += str_to_int(msg["message_text"])
|
|
||||||
msg["msgNo"] = total
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg["msgNo"] in from_msg_list[msg["from_call"]]) {
|
|
||||||
// We already have this message
|
|
||||||
//console.log("We already have this message msgNo=" + msg["msgNo"]);
|
|
||||||
// Do some flashy thing?
|
|
||||||
flash_message(msg);
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
from_msg_list[msg["from_call"]][msg["msgNo"]] = msg
|
|
||||||
}
|
|
||||||
info = time_ack_from_msg(msg);
|
|
||||||
t = info['time'];
|
|
||||||
d = info['date'];
|
|
||||||
ack_id = info['ack_id'];
|
|
||||||
|
|
||||||
from = msg['from_call']
|
|
||||||
msg_html = create_message_html(d, t, from, false, msg['message_text'], false, msg, false);
|
|
||||||
append_message(from, msg, msg_html);
|
|
||||||
save_data();
|
|
||||||
scroll_main_content(from);
|
|
||||||
reload_popovers();
|
|
||||||
}
|
|
||||||
|
|
||||||
function ack_msg(msg) {
|
|
||||||
// Acknowledge a message
|
|
||||||
// We have an existing entry
|
|
||||||
ts_id = message_ts_id(msg);
|
|
||||||
id = ts_id['id'];
|
|
||||||
//Mark the message as acked
|
|
||||||
callsign = msg['to_call'];
|
|
||||||
// Ensure the message_list has this callsign
|
|
||||||
if (!message_list.hasOwnProperty(callsign)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Ensure the message_list has this id
|
|
||||||
if (!message_list[callsign].hasOwnProperty(id)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (message_list[callsign][id]['ack'] == true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
message_list[callsign][id]['ack'] = true;
|
|
||||||
ack_id = "ack_" + id
|
|
||||||
|
|
||||||
if (msg['ack'] == true) {
|
|
||||||
var ack_div = $('#' + ack_id);
|
|
||||||
ack_div.html('thumb_up');
|
|
||||||
}
|
|
||||||
|
|
||||||
//$('.ui.accordion').accordion('refresh');
|
|
||||||
save_data();
|
|
||||||
scroll_main_content();
|
|
||||||
}
|
|
||||||
|
|
||||||
function activate_callsign_tab(callsign) {
|
|
||||||
tab_content = tab_string(callsign, id=true);
|
|
||||||
$(tab_content).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function callsign_select(callsign) {
|
|
||||||
var tocall = $("#to_call");
|
|
||||||
tocall.val(callsign.toUpperCase());
|
|
||||||
scroll_main_content(callsign);
|
|
||||||
selected_tab_callsign = callsign;
|
|
||||||
tab_notify_id = tab_notification_id(callsign, true);
|
|
||||||
$(tab_notify_id).addClass('visually-hidden');
|
|
||||||
$(tab_notify_id).text(0);
|
|
||||||
// Now update the path
|
|
||||||
// $('#pkt_path').val(callsign_list[callsign]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function call_callsign_location(callsign) {
|
|
||||||
msg = {'callsign': callsign};
|
|
||||||
socket.emit("get_callsign_location", msg);
|
|
||||||
location_id = callsign_location_content(callsign, true)+"Spinner";
|
|
||||||
$(location_id).removeClass('d-none');
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
function openTab(evt, tabName) {
|
|
||||||
// Declare all variables
|
|
||||||
var i, tabcontent, tablinks;
|
|
||||||
|
|
||||||
if (typeof tabName == 'undefined') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all elements with class="tabcontent" and hide them
|
|
||||||
tabcontent = document.getElementsByClassName("tabcontent");
|
|
||||||
for (i = 0; i < tabcontent.length; i++) {
|
|
||||||
tabcontent[i].style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all elements with class="tablinks" and remove the class "active"
|
|
||||||
tablinks = document.getElementsByClassName("tablinks");
|
|
||||||
for (i = 0; i < tablinks.length; i++) {
|
|
||||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
|
||||||
document.getElementById(tabName).style.display = "block";
|
|
||||||
if (typeof evt.currentTarget == 'undefined') {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
evt.currentTarget.className += " active";
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,374 +0,0 @@
|
|||||||
// jQuery toast plugin created by Kamran Ahmed copyright MIT license 2015
|
|
||||||
if ( typeof Object.create !== 'function' ) {
|
|
||||||
Object.create = function( obj ) {
|
|
||||||
function F() {}
|
|
||||||
F.prototype = obj;
|
|
||||||
return new F();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
(function( $, window, document, undefined ) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var Toast = {
|
|
||||||
|
|
||||||
_positionClasses : ['bottom-left', 'bottom-right', 'top-right', 'top-left', 'bottom-center', 'top-center', 'mid-center'],
|
|
||||||
_defaultIcons : ['success', 'error', 'info', 'warning'],
|
|
||||||
|
|
||||||
init: function (options, elem) {
|
|
||||||
this.prepareOptions(options, $.toast.options);
|
|
||||||
this.process();
|
|
||||||
},
|
|
||||||
|
|
||||||
prepareOptions: function(options, options_to_extend) {
|
|
||||||
var _options = {};
|
|
||||||
if ( ( typeof options === 'string' ) || ( options instanceof Array ) ) {
|
|
||||||
_options.text = options;
|
|
||||||
} else {
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
this.options = $.extend( {}, options_to_extend, _options );
|
|
||||||
},
|
|
||||||
|
|
||||||
process: function () {
|
|
||||||
this.setup();
|
|
||||||
this.addToDom();
|
|
||||||
this.position();
|
|
||||||
this.bindToast();
|
|
||||||
this.animate();
|
|
||||||
},
|
|
||||||
|
|
||||||
setup: function () {
|
|
||||||
|
|
||||||
var _toastContent = '';
|
|
||||||
|
|
||||||
this._toastEl = this._toastEl || $('<div></div>', {
|
|
||||||
class : 'jq-toast-single'
|
|
||||||
});
|
|
||||||
|
|
||||||
// For the loader on top
|
|
||||||
_toastContent += '<span class="jq-toast-loader"></span>';
|
|
||||||
|
|
||||||
if ( this.options.allowToastClose ) {
|
|
||||||
_toastContent += '<span class="close-jq-toast-single">×</span>';
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( this.options.text instanceof Array ) {
|
|
||||||
|
|
||||||
if ( this.options.heading ) {
|
|
||||||
_toastContent +='<h2 class="jq-toast-heading">' + this.options.heading + '</h2>';
|
|
||||||
};
|
|
||||||
|
|
||||||
_toastContent += '<ul class="jq-toast-ul">';
|
|
||||||
for (var i = 0; i < this.options.text.length; i++) {
|
|
||||||
_toastContent += '<li class="jq-toast-li" id="jq-toast-item-' + i + '">' + this.options.text[i] + '</li>';
|
|
||||||
}
|
|
||||||
_toastContent += '</ul>';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if ( this.options.heading ) {
|
|
||||||
_toastContent +='<h2 class="jq-toast-heading">' + this.options.heading + '</h2>';
|
|
||||||
};
|
|
||||||
_toastContent += this.options.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._toastEl.html( _toastContent );
|
|
||||||
|
|
||||||
if ( this.options.bgColor !== false ) {
|
|
||||||
this._toastEl.css("background-color", this.options.bgColor);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( this.options.textColor !== false ) {
|
|
||||||
this._toastEl.css("color", this.options.textColor);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( this.options.textAlign ) {
|
|
||||||
this._toastEl.css('text-align', this.options.textAlign);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( this.options.icon !== false ) {
|
|
||||||
this._toastEl.addClass('jq-has-icon');
|
|
||||||
|
|
||||||
if ( $.inArray(this.options.icon, this._defaultIcons) !== -1 ) {
|
|
||||||
this._toastEl.addClass('jq-icon-' + this.options.icon);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( this.options.class !== false ){
|
|
||||||
this._toastEl.addClass(this.options.class)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
position: function () {
|
|
||||||
if ( ( typeof this.options.position === 'string' ) && ( $.inArray( this.options.position, this._positionClasses) !== -1 ) ) {
|
|
||||||
|
|
||||||
if ( this.options.position === 'bottom-center' ) {
|
|
||||||
this._container.css({
|
|
||||||
left: ( $(window).outerWidth() / 2 ) - this._container.outerWidth()/2,
|
|
||||||
bottom: 20
|
|
||||||
});
|
|
||||||
} else if ( this.options.position === 'top-center' ) {
|
|
||||||
this._container.css({
|
|
||||||
left: ( $(window).outerWidth() / 2 ) - this._container.outerWidth()/2,
|
|
||||||
top: 20
|
|
||||||
});
|
|
||||||
} else if ( this.options.position === 'mid-center' ) {
|
|
||||||
this._container.css({
|
|
||||||
left: ( $(window).outerWidth() / 2 ) - this._container.outerWidth()/2,
|
|
||||||
top: ( $(window).outerHeight() / 2 ) - this._container.outerHeight()/2
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._container.addClass( this.options.position );
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if ( typeof this.options.position === 'object' ) {
|
|
||||||
this._container.css({
|
|
||||||
top : this.options.position.top ? this.options.position.top : 'auto',
|
|
||||||
bottom : this.options.position.bottom ? this.options.position.bottom : 'auto',
|
|
||||||
left : this.options.position.left ? this.options.position.left : 'auto',
|
|
||||||
right : this.options.position.right ? this.options.position.right : 'auto'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._container.addClass( 'bottom-left' );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bindToast: function () {
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this._toastEl.on('afterShown', function () {
|
|
||||||
that.processLoader();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._toastEl.find('.close-jq-toast-single').on('click', function ( e ) {
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if( that.options.showHideTransition === 'fade') {
|
|
||||||
that._toastEl.trigger('beforeHide');
|
|
||||||
that._toastEl.fadeOut(function () {
|
|
||||||
that._toastEl.trigger('afterHidden');
|
|
||||||
});
|
|
||||||
} else if ( that.options.showHideTransition === 'slide' ) {
|
|
||||||
that._toastEl.trigger('beforeHide');
|
|
||||||
that._toastEl.slideUp(function () {
|
|
||||||
that._toastEl.trigger('afterHidden');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
that._toastEl.trigger('beforeHide');
|
|
||||||
that._toastEl.hide(function () {
|
|
||||||
that._toastEl.trigger('afterHidden');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( typeof this.options.beforeShow == 'function' ) {
|
|
||||||
this._toastEl.on('beforeShow', function () {
|
|
||||||
that.options.beforeShow(that._toastEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( typeof this.options.afterShown == 'function' ) {
|
|
||||||
this._toastEl.on('afterShown', function () {
|
|
||||||
that.options.afterShown(that._toastEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( typeof this.options.beforeHide == 'function' ) {
|
|
||||||
this._toastEl.on('beforeHide', function () {
|
|
||||||
that.options.beforeHide(that._toastEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( typeof this.options.afterHidden == 'function' ) {
|
|
||||||
this._toastEl.on('afterHidden', function () {
|
|
||||||
that.options.afterHidden(that._toastEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( typeof this.options.onClick == 'function' ) {
|
|
||||||
this._toastEl.on('click', function () {
|
|
||||||
that.options.onClick(that._toastEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
addToDom: function () {
|
|
||||||
|
|
||||||
var _container = $('.jq-toast-wrap');
|
|
||||||
|
|
||||||
if ( _container.length === 0 ) {
|
|
||||||
|
|
||||||
_container = $('<div></div>',{
|
|
||||||
class: "jq-toast-wrap",
|
|
||||||
role: "alert",
|
|
||||||
"aria-live": "polite"
|
|
||||||
});
|
|
||||||
|
|
||||||
$('body').append( _container );
|
|
||||||
|
|
||||||
} else if ( !this.options.stack || isNaN( parseInt(this.options.stack, 10) ) ) {
|
|
||||||
_container.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
_container.find('.jq-toast-single:hidden').remove();
|
|
||||||
|
|
||||||
_container.append( this._toastEl );
|
|
||||||
|
|
||||||
if ( this.options.stack && !isNaN( parseInt( this.options.stack ), 10 ) ) {
|
|
||||||
|
|
||||||
var _prevToastCount = _container.find('.jq-toast-single').length,
|
|
||||||
_extToastCount = _prevToastCount - this.options.stack;
|
|
||||||
|
|
||||||
if ( _extToastCount > 0 ) {
|
|
||||||
$('.jq-toast-wrap').find('.jq-toast-single').slice(0, _extToastCount).remove();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
this._container = _container;
|
|
||||||
},
|
|
||||||
|
|
||||||
canAutoHide: function () {
|
|
||||||
return ( this.options.hideAfter !== false ) && !isNaN( parseInt( this.options.hideAfter, 10 ) );
|
|
||||||
},
|
|
||||||
|
|
||||||
processLoader: function () {
|
|
||||||
// Show the loader only, if auto-hide is on and loader is demanded
|
|
||||||
if (!this.canAutoHide() || this.options.loader === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var loader = this._toastEl.find('.jq-toast-loader');
|
|
||||||
|
|
||||||
// 400 is the default time that jquery uses for fade/slide
|
|
||||||
// Divide by 1000 for milliseconds to seconds conversion
|
|
||||||
var transitionTime = (this.options.hideAfter - 400) / 1000 + 's';
|
|
||||||
var loaderBg = this.options.loaderBg;
|
|
||||||
|
|
||||||
var style = loader.attr('style') || '';
|
|
||||||
style = style.substring(0, style.indexOf('-webkit-transition')); // Remove the last transition definition
|
|
||||||
|
|
||||||
style += '-webkit-transition: width ' + transitionTime + ' ease-in; \
|
|
||||||
-o-transition: width ' + transitionTime + ' ease-in; \
|
|
||||||
transition: width ' + transitionTime + ' ease-in; \
|
|
||||||
background-color: ' + loaderBg + ';';
|
|
||||||
|
|
||||||
|
|
||||||
loader.attr('style', style).addClass('jq-toast-loaded');
|
|
||||||
},
|
|
||||||
|
|
||||||
animate: function () {
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
this._toastEl.hide();
|
|
||||||
|
|
||||||
this._toastEl.trigger('beforeShow');
|
|
||||||
|
|
||||||
if ( this.options.showHideTransition.toLowerCase() === 'fade' ) {
|
|
||||||
this._toastEl.fadeIn(function ( ){
|
|
||||||
that._toastEl.trigger('afterShown');
|
|
||||||
});
|
|
||||||
} else if ( this.options.showHideTransition.toLowerCase() === 'slide' ) {
|
|
||||||
this._toastEl.slideDown(function ( ){
|
|
||||||
that._toastEl.trigger('afterShown');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._toastEl.show(function ( ){
|
|
||||||
that._toastEl.trigger('afterShown');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.canAutoHide()) {
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
window.setTimeout(function(){
|
|
||||||
|
|
||||||
if ( that.options.showHideTransition.toLowerCase() === 'fade' ) {
|
|
||||||
that._toastEl.trigger('beforeHide');
|
|
||||||
that._toastEl.fadeOut(function () {
|
|
||||||
that._toastEl.trigger('afterHidden');
|
|
||||||
});
|
|
||||||
} else if ( that.options.showHideTransition.toLowerCase() === 'slide' ) {
|
|
||||||
that._toastEl.trigger('beforeHide');
|
|
||||||
that._toastEl.slideUp(function () {
|
|
||||||
that._toastEl.trigger('afterHidden');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
that._toastEl.trigger('beforeHide');
|
|
||||||
that._toastEl.hide(function () {
|
|
||||||
that._toastEl.trigger('afterHidden');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}, this.options.hideAfter);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
reset: function ( resetWhat ) {
|
|
||||||
|
|
||||||
if ( resetWhat === 'all' ) {
|
|
||||||
$('.jq-toast-wrap').remove();
|
|
||||||
} else {
|
|
||||||
this._toastEl.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function(options) {
|
|
||||||
this.prepareOptions(options, this.options);
|
|
||||||
this.setup();
|
|
||||||
this.bindToast();
|
|
||||||
},
|
|
||||||
|
|
||||||
close: function() {
|
|
||||||
this._toastEl.find('.close-jq-toast-single').click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.toast = function(options) {
|
|
||||||
var toast = Object.create(Toast);
|
|
||||||
toast.init(options, this);
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
reset: function ( what ) {
|
|
||||||
toast.reset( what );
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function( options ) {
|
|
||||||
toast.update( options );
|
|
||||||
},
|
|
||||||
|
|
||||||
close: function( ) {
|
|
||||||
toast.close( );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.toast.options = {
|
|
||||||
text: '',
|
|
||||||
heading: '',
|
|
||||||
showHideTransition: 'fade',
|
|
||||||
allowToastClose: true,
|
|
||||||
hideAfter: 3000,
|
|
||||||
loader: true,
|
|
||||||
loaderBg: '#9EC600',
|
|
||||||
stack: 5,
|
|
||||||
position: 'bottom-left',
|
|
||||||
bgColor: false,
|
|
||||||
textColor: false,
|
|
||||||
textAlign: 'left',
|
|
||||||
icon: false,
|
|
||||||
beforeShow: function () {},
|
|
||||||
afterShown: function () {},
|
|
||||||
beforeHide: function () {},
|
|
||||||
afterHidden: function () {},
|
|
||||||
onClick: function () {}
|
|
||||||
};
|
|
||||||
|
|
||||||
})( jQuery, window, document );
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,139 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
|
||||||
<script src="/static/js/upstream/jquery-3.7.1.min.js"></script>
|
|
||||||
<script src="/static/js/upstream/jquery.toast.js"></script>
|
|
||||||
<script src="/static/js/upstream/socket.io.min.js"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/upstream/bootstrap.min.css">
|
|
||||||
<script src="/static/js/upstream/bootstrap.bundle.min.js"></script>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9"
|
|
||||||
crossorigin="anonymous">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
|
|
||||||
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/upstream/google-fonts.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/upstream/jquery.toast.css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/chat.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/index.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/tabs.css">
|
|
||||||
<script src="/static/js/main.js"></script>
|
|
||||||
<script src="/static/js/gps.js"></script>
|
|
||||||
<script src="/static/js/send-message.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var initial_stats = {{ initial_stats|tojson|safe }};
|
|
||||||
var latitude = parseFloat('{{ latitude|safe }}');
|
|
||||||
var longitude = parseFloat('{{ longitude|safe }}');
|
|
||||||
|
|
||||||
var memory_chart = null;
|
|
||||||
var message_chart = null;
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
console.log(initial_stats);
|
|
||||||
start_update();
|
|
||||||
init_chat();
|
|
||||||
//reset_Tabs();
|
|
||||||
|
|
||||||
console.log("latitude", latitude);
|
|
||||||
console.log("longitude", longitude);
|
|
||||||
|
|
||||||
if (isNaN(latitude) || isNaN(longitude) && location.protocol != 'https:') {
|
|
||||||
// Have to disable the beacon button.
|
|
||||||
$('#send_beacon').prop('disabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#wipe_local").click(function() {
|
|
||||||
console.log('Wipe local storage');
|
|
||||||
localStorage.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
// When a tab is clicked, populate the to_call form field.
|
|
||||||
$(document).on('shown.bs.tab', 'button[data-bs-toggle="tab"]', function (e) {
|
|
||||||
var tab = $(e.target);
|
|
||||||
var callsign = tab.attr("callsign");
|
|
||||||
var to_call = $('#to_call');
|
|
||||||
to_call.val(callsign);
|
|
||||||
selected_tab_callsign = callsign;
|
|
||||||
});
|
|
||||||
|
|
||||||
/*$('[data-bs-toggle="popover"]').popover(
|
|
||||||
{html: true, animation: true}
|
|
||||||
);*/
|
|
||||||
reload_popovers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="wc-container">
|
|
||||||
<div class="wc-row header">
|
|
||||||
<div class="container-sm">
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<h1>APRSD WebChat {{ version }}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="column">
|
|
||||||
<span style='color: green'>{{ callsign }}</span>
|
|
||||||
connected to
|
|
||||||
<span style='color: blue' id='aprs_connection'>{{ aprs_connection|safe }}</span>
|
|
||||||
<span id='uptime'>NONE</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<form class="row gx-1 gy-1 justify-content-center align-items-center" id="sendform" name="sendmsg" action="" autocomplete="off">
|
|
||||||
<div class="col-sm-2" style="width:150px;">
|
|
||||||
<label for="to_call" class="visually-hidden">Callsign</label>
|
|
||||||
<input type="search" class="form-control mb-2 mr-sm-2" name="to_call" id="to_call" placeholder="To Callsign" size="11" maxlength="9">
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<label for="pkt_path" class="visually-hidden">PATH</label>
|
|
||||||
<select class="form-control mb-2 mr-sm-2" name="pkt_path" id="pkt_path" style="width:auto;">
|
|
||||||
<option value="" disabled selected>Default Path</option>
|
|
||||||
<option value="WIDE1-1">WIDE1-1</option>
|
|
||||||
<option value="WIDE1-1,WIDE2-1">WIDE1-1,WIDE2-1</option>
|
|
||||||
<option value="ARISS">ARISS</option>
|
|
||||||
<option value="GATE">GATE</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<label for="message" class="visually-hidden">Message</label>
|
|
||||||
<input type="search" class="form-control mb-2 mr-sm-2" name="message" id="message" size="40" maxlength="67" placeholder="Message">
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<input type="submit" name="submit" class="btn btn-primary mb-2" id="send_msg" value="Send"/>
|
|
||||||
<button type="button" class="btn btn-primary mb-2" id="send_beacon" value="Send Position">Send Position</button>
|
|
||||||
<!-- <button type="button" class="btn btn-primary mb-2" id="wipe_local" value="wipe local storage">Wipe LocalStorage</button> -->
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container-sm" style="max-width: 800px">
|
|
||||||
<ul class="nav nav-tabs" id="msgsTabList" role="tablist"></ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wc-row content" id="wc-content">
|
|
||||||
<div class="container" style="max-width: 800px;">
|
|
||||||
<div class="tab-content" id="msgsTabContent">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wc-row footer">
|
|
||||||
<div class="container-sm" 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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.10
|
# This file is autogenerated by pip-compile with Python 3.12
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --annotation-style=line requirements-dev.in
|
# pip-compile --annotation-style=line requirements-dev.in
|
||||||
@ -20,10 +20,9 @@ click==8.1.7 # via black, fixit, moreorless, pip-tools
|
|||||||
colorama==0.4.6 # via tox
|
colorama==0.4.6 # via tox
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
configargparse==1.7 # via gray
|
configargparse==1.7 # via gray
|
||||||
coverage[toml]==7.6.8 # via pytest-cov
|
coverage[toml]==7.6.9 # via pytest-cov
|
||||||
distlib==0.3.9 # via virtualenv
|
distlib==0.3.9 # via virtualenv
|
||||||
docutils==0.21.2 # via m2r, sphinx
|
docutils==0.21.2 # via m2r, sphinx
|
||||||
exceptiongroup==1.2.2 # via pytest
|
|
||||||
filelock==3.16.1 # via tox, virtualenv
|
filelock==3.16.1 # via tox, virtualenv
|
||||||
fixit==2.1.0 # via gray
|
fixit==2.1.0 # via gray
|
||||||
flake8==7.1.1 # via -r requirements-dev.in, pep8-naming
|
flake8==7.1.1 # via -r requirements-dev.in, pep8-naming
|
||||||
@ -71,10 +70,9 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx
|
|||||||
sphinxcontrib-serializinghtml==2.0.0 # via sphinx
|
sphinxcontrib-serializinghtml==2.0.0 # via sphinx
|
||||||
tokenize-rt==6.1.0 # via add-trailing-comma, pyupgrade
|
tokenize-rt==6.1.0 # via add-trailing-comma, pyupgrade
|
||||||
toml==0.10.2 # via autoflake
|
toml==0.10.2 # via autoflake
|
||||||
tomli==2.2.1 # via black, build, check-manifest, coverage, fixit, mypy, pip-tools, pyproject-api, pytest, sphinx, tox
|
|
||||||
tox==4.23.2 # via -r requirements-dev.in
|
tox==4.23.2 # via -r requirements-dev.in
|
||||||
trailrunner==1.4.0 # via fixit
|
trailrunner==1.4.0 # via fixit
|
||||||
typing-extensions==4.12.2 # via black, mypy, tox
|
typing-extensions==4.12.2 # via mypy
|
||||||
unify==0.5 # via gray
|
unify==0.5 # via gray
|
||||||
untokenize==0.1.1 # via unify
|
untokenize==0.1.1 # via unify
|
||||||
urllib3==2.2.3 # via requests
|
urllib3==2.2.3 # via requests
|
||||||
|
@ -2,25 +2,17 @@ aprslib>=0.7.0
|
|||||||
# For the list-plugins pypi.org search scraping
|
# For the list-plugins pypi.org search scraping
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
click
|
click
|
||||||
click-params
|
|
||||||
dataclasses-json
|
dataclasses-json
|
||||||
flask
|
|
||||||
flask-httpauth
|
|
||||||
flask-socketio
|
|
||||||
geopy
|
geopy
|
||||||
imapclient
|
imapclient
|
||||||
kiss3
|
kiss3
|
||||||
loguru
|
loguru
|
||||||
oslo.config
|
oslo.config
|
||||||
pluggy
|
pluggy
|
||||||
python-socketio
|
|
||||||
requests
|
requests
|
||||||
# Pinned due to gray needing 12.6.0
|
# Pinned due to gray needing 12.6.0
|
||||||
rich~=12.6.0
|
rich~=12.6.0
|
||||||
rush
|
rush
|
||||||
shellingham
|
|
||||||
six
|
|
||||||
tabulate
|
|
||||||
thesmuggler
|
thesmuggler
|
||||||
tzlocal
|
tzlocal
|
||||||
update_checker
|
update_checker
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.10
|
# This file is autogenerated by pip-compile with Python 3.12
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --annotation-style=line requirements.in
|
# pip-compile --annotation-style=line requirements.in
|
||||||
@ -8,31 +8,20 @@ aprslib==0.7.2 # via -r requirements.in
|
|||||||
attrs==24.2.0 # via ax253, kiss3, rush
|
attrs==24.2.0 # via ax253, kiss3, rush
|
||||||
ax253==0.1.5.post1 # via kiss3
|
ax253==0.1.5.post1 # via kiss3
|
||||||
beautifulsoup4==4.12.3 # via -r requirements.in
|
beautifulsoup4==4.12.3 # via -r requirements.in
|
||||||
bidict==0.23.1 # via python-socketio
|
|
||||||
bitarray==3.0.0 # via ax253, kiss3
|
bitarray==3.0.0 # via ax253, kiss3
|
||||||
blinker==1.9.0 # via flask
|
|
||||||
certifi==2024.8.30 # via requests
|
certifi==2024.8.30 # via requests
|
||||||
charset-normalizer==3.4.0 # via requests
|
charset-normalizer==3.4.0 # via requests
|
||||||
click==8.1.7 # via -r requirements.in, click-params, flask
|
click==8.1.7 # via -r requirements.in
|
||||||
click-params==0.5.0 # via -r requirements.in
|
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
dataclasses-json==0.6.7 # via -r requirements.in
|
dataclasses-json==0.6.7 # via -r requirements.in
|
||||||
debtcollector==3.0.0 # via oslo-config
|
debtcollector==3.0.0 # via oslo-config
|
||||||
deprecated==1.2.15 # via click-params
|
|
||||||
flask==3.1.0 # via -r requirements.in, flask-httpauth, flask-socketio
|
|
||||||
flask-httpauth==4.8.0 # via -r requirements.in
|
|
||||||
flask-socketio==5.4.1 # via -r requirements.in
|
|
||||||
geographiclib==2.0 # via geopy
|
geographiclib==2.0 # via geopy
|
||||||
geopy==2.4.1 # via -r requirements.in
|
geopy==2.4.1 # via -r requirements.in
|
||||||
h11==0.14.0 # via wsproto
|
|
||||||
idna==3.10 # via requests
|
idna==3.10 # via requests
|
||||||
imapclient==3.0.1 # via -r requirements.in
|
imapclient==3.0.1 # via -r requirements.in
|
||||||
importlib-metadata==8.5.0 # via ax253, kiss3
|
importlib-metadata==8.5.0 # via ax253, kiss3
|
||||||
itsdangerous==2.2.0 # via flask
|
|
||||||
jinja2==3.1.4 # via flask
|
|
||||||
kiss3==8.0.0 # via -r requirements.in
|
kiss3==8.0.0 # via -r requirements.in
|
||||||
loguru==0.7.2 # via -r requirements.in
|
loguru==0.7.3 # via -r requirements.in
|
||||||
markupsafe==3.0.2 # via jinja2, werkzeug
|
|
||||||
marshmallow==3.23.1 # via dataclasses-json
|
marshmallow==3.23.1 # via dataclasses-json
|
||||||
mypy-extensions==1.0.0 # via typing-inspect
|
mypy-extensions==1.0.0 # via typing-inspect
|
||||||
netaddr==1.3.0 # via oslo-config
|
netaddr==1.3.0 # via oslo-config
|
||||||
@ -44,20 +33,14 @@ pluggy==1.5.0 # via -r requirements.in
|
|||||||
pygments==2.18.0 # via rich
|
pygments==2.18.0 # via rich
|
||||||
pyserial==3.5 # via pyserial-asyncio
|
pyserial==3.5 # via pyserial-asyncio
|
||||||
pyserial-asyncio==0.6 # via kiss3
|
pyserial-asyncio==0.6 # via kiss3
|
||||||
python-engineio==4.10.1 # via python-socketio
|
|
||||||
python-socketio==5.11.4 # via -r requirements.in, flask-socketio
|
|
||||||
pytz==2024.2 # via -r requirements.in
|
pytz==2024.2 # via -r requirements.in
|
||||||
pyyaml==6.0.2 # via oslo-config
|
pyyaml==6.0.2 # via oslo-config
|
||||||
requests==2.32.3 # via -r requirements.in, oslo-config, update-checker
|
requests==2.32.3 # via -r requirements.in, oslo-config, update-checker
|
||||||
rfc3986==2.0.0 # via oslo-config
|
rfc3986==2.0.0 # via oslo-config
|
||||||
rich==12.6.0 # via -r requirements.in
|
rich==12.6.0 # via -r requirements.in
|
||||||
rush==2021.4.0 # via -r requirements.in
|
rush==2021.4.0 # via -r requirements.in
|
||||||
shellingham==1.5.4 # via -r requirements.in
|
|
||||||
simple-websocket==1.1.0 # via python-engineio
|
|
||||||
six==1.16.0 # via -r requirements.in
|
|
||||||
soupsieve==2.6 # via beautifulsoup4
|
soupsieve==2.6 # via beautifulsoup4
|
||||||
stevedore==5.4.0 # via oslo-config
|
stevedore==5.4.0 # via oslo-config
|
||||||
tabulate==0.9.0 # via -r requirements.in
|
|
||||||
thesmuggler==1.0.1 # via -r requirements.in
|
thesmuggler==1.0.1 # via -r requirements.in
|
||||||
timeago==1.0.16 # via -r requirements.in
|
timeago==1.0.16 # via -r requirements.in
|
||||||
typing-extensions==4.12.2 # via typing-inspect
|
typing-extensions==4.12.2 # via typing-inspect
|
||||||
@ -65,8 +48,5 @@ typing-inspect==0.9.0 # via dataclasses-json
|
|||||||
tzlocal==5.2 # via -r requirements.in
|
tzlocal==5.2 # via -r requirements.in
|
||||||
update-checker==0.18.0 # via -r requirements.in
|
update-checker==0.18.0 # via -r requirements.in
|
||||||
urllib3==2.2.3 # via requests
|
urllib3==2.2.3 # via requests
|
||||||
validators==0.22.0 # via click-params
|
wrapt==1.17.0 # via -r requirements.in, debtcollector
|
||||||
werkzeug==3.1.3 # via flask
|
|
||||||
wrapt==1.17.0 # via -r requirements.in, debtcollector, deprecated
|
|
||||||
wsproto==1.2.0 # via simple-websocket
|
|
||||||
zipp==3.21.0 # via importlib-metadata
|
zipp==3.21.0 # via importlib-metadata
|
||||||
|
@ -27,8 +27,8 @@ class TestSendMessageCommand(unittest.TestCase):
|
|||||||
if password:
|
if password:
|
||||||
CONF.aprs_network.password = password
|
CONF.aprs_network.password = password
|
||||||
|
|
||||||
CONF.admin.user = "admin"
|
# CONF.aprsd_admin_extension.user = "admin"
|
||||||
CONF.admin.password = "password"
|
# CONF.aprsd_admin_extension.password = "password"
|
||||||
|
|
||||||
@mock.patch("aprsd.log.log.setup_logging")
|
@mock.patch("aprsd.log.log.setup_logging")
|
||||||
def test_no_tocallsign(self, mock_logging):
|
def test_no_tocallsign(self, mock_logging):
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import typing as t
|
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
|
||||||
import flask
|
|
||||||
import flask_socketio
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from aprsd import conf # noqa: F401
|
|
||||||
from aprsd.client import fake as fake_client
|
|
||||||
from aprsd.cmds import webchat # noqa
|
|
||||||
from aprsd.packets import core
|
|
||||||
|
|
||||||
from .. import fake
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
|
||||||
|
|
||||||
|
|
||||||
class TestSendMessageCommand(unittest.TestCase):
|
|
||||||
|
|
||||||
def config_and_init(self, login=None, password=None):
|
|
||||||
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
|
||||||
CONF.trace_enabled = False
|
|
||||||
CONF.watch_list.packet_keep_count = 1
|
|
||||||
if login:
|
|
||||||
CONF.aprs_network.login = login
|
|
||||||
if password:
|
|
||||||
CONF.aprs_network.password = password
|
|
||||||
|
|
||||||
CONF.admin.user = "admin"
|
|
||||||
CONF.admin.password = "password"
|
|
||||||
|
|
||||||
@mock.patch("aprsd.log.log.setup_logging")
|
|
||||||
def test_init_flask(self, mock_logging):
|
|
||||||
"""Make sure we get an error if there is no login and config."""
|
|
||||||
|
|
||||||
CliRunner()
|
|
||||||
self.config_and_init()
|
|
||||||
|
|
||||||
socketio = webchat.init_flask("DEBUG", False)
|
|
||||||
self.assertIsInstance(socketio, flask_socketio.SocketIO)
|
|
||||||
self.assertIsInstance(webchat.flask_app, flask.Flask)
|
|
||||||
|
|
||||||
@mock.patch("aprsd.packets.tracker.PacketTrack.remove")
|
|
||||||
@mock.patch("aprsd.cmds.webchat.socketio")
|
|
||||||
def test_process_ack_packet(
|
|
||||||
self,
|
|
||||||
mock_remove, mock_socketio,
|
|
||||||
):
|
|
||||||
self.config_and_init()
|
|
||||||
mock_socketio.emit = mock.MagicMock()
|
|
||||||
# Create an ACK packet
|
|
||||||
packet = fake.fake_ack_packet()
|
|
||||||
mock_queue = mock.MagicMock()
|
|
||||||
socketio = mock.MagicMock()
|
|
||||||
wcp = webchat.WebChatProcessPacketThread(mock_queue, socketio)
|
|
||||||
|
|
||||||
wcp.process_ack_packet(packet)
|
|
||||||
mock_remove.called_once()
|
|
||||||
mock_socketio.called_once()
|
|
||||||
|
|
||||||
@mock.patch("aprsd.threads.tx.send")
|
|
||||||
@mock.patch("aprsd.packets.PacketList.rx")
|
|
||||||
@mock.patch("aprsd.cmds.webchat.socketio")
|
|
||||||
@mock.patch("aprsd.client.factory.ClientFactory.create")
|
|
||||||
def test_process_our_message_packet(
|
|
||||||
self,
|
|
||||||
mock_tx_send,
|
|
||||||
mock_packet_add,
|
|
||||||
mock_socketio,
|
|
||||||
mock_factory,
|
|
||||||
):
|
|
||||||
self.config_and_init()
|
|
||||||
mock_socketio.emit = mock.MagicMock()
|
|
||||||
packet = fake.fake_packet(
|
|
||||||
message="blah",
|
|
||||||
msg_number=1,
|
|
||||||
message_format=core.PACKET_TYPE_MESSAGE,
|
|
||||||
)
|
|
||||||
mock_factory.return_value = fake_client.APRSDFakeClient()
|
|
||||||
mock_queue = mock.MagicMock()
|
|
||||||
socketio = mock.MagicMock()
|
|
||||||
wcp = webchat.WebChatProcessPacketThread(mock_queue, socketio)
|
|
||||||
|
|
||||||
wcp.process_our_message_packet(packet)
|
|
||||||
mock_packet_add.called_once()
|
|
||||||
mock_socketio.called_once()
|
|
Loading…
x
Reference in New Issue
Block a user