mirror of
https://github.com/craigerl/aprsd.git
synced 2026-08-17 17:13:49 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 691b18fd1c | |||
| 911730b28a | |||
| 349250685b | |||
| 840c8a990e | |||
| ed4995b6eb | |||
| 6740ff80be | |||
| be8179415a |
@@ -1,9 +1,19 @@
|
|||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
v2.1.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Enable multiple replies for plugins
|
||||||
|
* Put in a fix for aprslib parse exceptions
|
||||||
|
* Fixed time plugin
|
||||||
|
* Updated the charts Added the packets chart
|
||||||
|
* Added showing symbol images to watch list
|
||||||
|
|
||||||
v2.0.0
|
v2.0.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
* Updated docs for 2.0.0
|
||||||
* Reworked the notification threads and admin ui
|
* Reworked the notification threads and admin ui
|
||||||
* Fixed small bug with packets get\_packet\_type
|
* Fixed small bug with packets get\_packet\_type
|
||||||
* Updated overview images
|
* Updated overview images
|
||||||
|
|||||||
+13
-3
@@ -224,11 +224,21 @@ class Aprsdis(aprslib.IS):
|
|||||||
self.logger.debug("Server: %s", line.decode("utf8"))
|
self.logger.debug("Server: %s", line.decode("utf8"))
|
||||||
stats.APRSDStats().set_aprsis_keepalive()
|
stats.APRSDStats().set_aprsis_keepalive()
|
||||||
except ParseError as exp:
|
except ParseError as exp:
|
||||||
self.logger.log(11, "%s\n Packet: %s", exp.args[0], exp.args[1])
|
self.logger.log(
|
||||||
|
11,
|
||||||
|
"%s\n Packet: %s",
|
||||||
|
exp,
|
||||||
|
exp.packet,
|
||||||
|
)
|
||||||
except UnknownFormat as exp:
|
except UnknownFormat as exp:
|
||||||
self.logger.log(9, "unknown format %s", exp.args)
|
self.logger.log(
|
||||||
|
9,
|
||||||
|
"%s\n Packet: %s",
|
||||||
|
exp,
|
||||||
|
exp.packet,
|
||||||
|
)
|
||||||
except LoginError as exp:
|
except LoginError as exp:
|
||||||
self.logger.error("%s: %s", exp.__class__.__name__, exp.args[0])
|
self.logger.error("%s: %s", exp.__class__.__name__, exp)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except (ConnectionDrop, ConnectionError):
|
except (ConnectionDrop, ConnectionError):
|
||||||
|
|||||||
+8
-1
@@ -121,6 +121,13 @@ class APRSDFlask(flask_classful.FlaskView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
stats_dict["aprsd"]["watch_list"] = new_list
|
stats_dict["aprsd"]["watch_list"] = new_list
|
||||||
|
packet_list = packets.PacketList()
|
||||||
|
rx = packet_list.total_received()
|
||||||
|
tx = packet_list.total_sent()
|
||||||
|
stats_dict["packets"] = {
|
||||||
|
"sent": tx,
|
||||||
|
"received": rx,
|
||||||
|
}
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"time": now.strftime(time_format),
|
"time": now.strftime(time_format),
|
||||||
@@ -170,7 +177,7 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
|||||||
def init_flask(config, loglevel, quiet):
|
def init_flask(config, loglevel, quiet):
|
||||||
flask_app = flask.Flask(
|
flask_app = flask.Flask(
|
||||||
"aprsd",
|
"aprsd",
|
||||||
static_url_path="",
|
static_url_path="/static",
|
||||||
static_folder="web/static",
|
static_folder="web/static",
|
||||||
template_folder="web/templates",
|
template_folder="web/templates",
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-1
@@ -32,7 +32,18 @@ import time
|
|||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import client, email, flask, messaging, plugin, stats, threads, trace, utils
|
from aprsd import (
|
||||||
|
client,
|
||||||
|
email,
|
||||||
|
flask,
|
||||||
|
messaging,
|
||||||
|
packets,
|
||||||
|
plugin,
|
||||||
|
stats,
|
||||||
|
threads,
|
||||||
|
trace,
|
||||||
|
utils,
|
||||||
|
)
|
||||||
import aprslib
|
import aprslib
|
||||||
from aprslib.exceptions import LoginError
|
from aprslib.exceptions import LoginError
|
||||||
import click
|
import click
|
||||||
@@ -519,6 +530,7 @@ def server(
|
|||||||
"enabled",
|
"enabled",
|
||||||
True,
|
True,
|
||||||
):
|
):
|
||||||
|
packets.PacketList(config)
|
||||||
notify_thread = threads.APRSDNotifyThread(
|
notify_thread = threads.APRSDNotifyThread(
|
||||||
msg_queues=msg_queues,
|
msg_queues=msg_queues,
|
||||||
config=config,
|
config=config,
|
||||||
|
|||||||
@@ -16,9 +16,13 @@ class PacketList:
|
|||||||
"""Class to track all of the packets rx'd and tx'd by aprsd."""
|
"""Class to track all of the packets rx'd and tx'd by aprsd."""
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
config = None
|
||||||
|
|
||||||
packet_list = {}
|
packet_list = {}
|
||||||
|
|
||||||
|
total_recv = 0
|
||||||
|
total_tx = 0
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
@@ -26,6 +30,10 @@ class PacketList:
|
|||||||
cls._instance.lock = threading.Lock()
|
cls._instance.lock = threading.Lock()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self, config=None):
|
||||||
|
if config:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return iter(self.packet_list)
|
return iter(self.packet_list)
|
||||||
@@ -33,12 +41,22 @@ class PacketList:
|
|||||||
def add(self, packet):
|
def add(self, packet):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
packet["ts"] = time.time()
|
packet["ts"] = time.time()
|
||||||
|
if "from" in packet and packet["from"] == self.config["aprs"]["login"]:
|
||||||
|
self.total_tx += 1
|
||||||
|
else:
|
||||||
|
self.total_recv += 1
|
||||||
self.packet_list.append(packet)
|
self.packet_list.append(packet)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return self.packet_list.get()
|
return self.packet_list.get()
|
||||||
|
|
||||||
|
def total_received(self):
|
||||||
|
return self.total_recv
|
||||||
|
|
||||||
|
def total_sent(self):
|
||||||
|
return self.total_tx
|
||||||
|
|
||||||
|
|
||||||
class WatchList:
|
class WatchList:
|
||||||
"""Global watch list and info for callsigns."""
|
"""Global watch list and info for callsigns."""
|
||||||
|
|||||||
+49
-6
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from aprsd import fuzzyclock, plugin, plugin_utils, trace, utils
|
from aprsd import fuzzyclock, plugin, plugin_utils, trace, utils
|
||||||
@@ -56,17 +57,38 @@ class TimeOpenCageDataPlugin(TimePlugin):
|
|||||||
@trace.trace
|
@trace.trace
|
||||||
def command(self, packet):
|
def command(self, packet):
|
||||||
fromcall = packet.get("from")
|
fromcall = packet.get("from")
|
||||||
# message = packet.get("message_text", None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
# get last location of a callsign, get descriptive name from weather service
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error("Failed to find config aprs.fi:apikey {}".format(ex))
|
||||||
|
return "No aprs.fi apikey found"
|
||||||
|
|
||||||
|
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
||||||
|
|
||||||
|
# optional second argument is a callsign to search
|
||||||
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
|
if a is not None:
|
||||||
|
searchcall = a.group(1)
|
||||||
|
searchcall = searchcall.upper()
|
||||||
|
else:
|
||||||
|
# if no second argument, search for calling station
|
||||||
|
searchcall = fromcall
|
||||||
|
|
||||||
|
try:
|
||||||
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
||||||
return "Failed to fetch location"
|
return "Failed to fetch location"
|
||||||
|
|
||||||
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
||||||
|
if not len(aprs_data["entries"]):
|
||||||
|
LOG.error("Didn't get any entries from aprs.fi")
|
||||||
|
return "Failed to fetch aprs.fi location"
|
||||||
|
|
||||||
lat = aprs_data["entries"][0]["lat"]
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
@@ -101,16 +123,37 @@ class TimeOWMPlugin(TimePlugin):
|
|||||||
@trace.trace
|
@trace.trace
|
||||||
def command(self, packet):
|
def command(self, packet):
|
||||||
fromcall = packet.get("from")
|
fromcall = packet.get("from")
|
||||||
# message = packet.get("message_text", None)
|
message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
|
|
||||||
|
# get last location of a callsign, get descriptive name from weather service
|
||||||
|
try:
|
||||||
|
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error("Failed to find config aprs.fi:apikey {}".format(ex))
|
||||||
|
return "No aprs.fi apikey found"
|
||||||
|
|
||||||
|
# optional second argument is a callsign to search
|
||||||
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
|
if a is not None:
|
||||||
|
searchcall = a.group(1)
|
||||||
|
searchcall = searchcall.upper()
|
||||||
|
else:
|
||||||
|
# if no second argument, search for calling station
|
||||||
|
searchcall = fromcall
|
||||||
|
|
||||||
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
api_key = self.config["services"]["aprs.fi"]["apiKey"]
|
||||||
try:
|
try:
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
||||||
return "Failed to fetch location"
|
return "Failed to fetch location"
|
||||||
|
|
||||||
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
||||||
|
if not len(aprs_data["entries"]):
|
||||||
|
LOG.error("Didn't get any entries from aprs.fi")
|
||||||
|
return "Failed to fetch aprs.fi location"
|
||||||
|
|
||||||
lat = aprs_data["entries"][0]["lat"]
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
|
|||||||
+27
-13
@@ -253,21 +253,35 @@ class APRSDRXThread(APRSDThread):
|
|||||||
try:
|
try:
|
||||||
results = pm.run(packet)
|
results = pm.run(packet)
|
||||||
for reply in results:
|
for reply in results:
|
||||||
found_command = True
|
if isinstance(reply, list):
|
||||||
# A plugin can return a null message flag which signals
|
# one of the plugins wants to send multiple messages
|
||||||
# us that they processed the message correctly, but have
|
found_command = True
|
||||||
# nothing to reply with, so we avoid replying with a usage string
|
for subreply in reply:
|
||||||
if reply is not messaging.NULL_MESSAGE:
|
LOG.debug("Sending '{}'".format(subreply))
|
||||||
LOG.debug("Sending '{}'".format(reply))
|
|
||||||
|
msg = messaging.TextMessage(
|
||||||
|
self.config["aprs"]["login"],
|
||||||
|
fromcall,
|
||||||
|
subreply,
|
||||||
|
)
|
||||||
|
self.msg_queues["tx"].put(msg)
|
||||||
|
|
||||||
msg = messaging.TextMessage(
|
|
||||||
self.config["aprs"]["login"],
|
|
||||||
fromcall,
|
|
||||||
reply,
|
|
||||||
)
|
|
||||||
self.msg_queues["tx"].put(msg)
|
|
||||||
else:
|
else:
|
||||||
LOG.debug("Got NULL MESSAGE from plugin")
|
found_command = True
|
||||||
|
# A plugin can return a null message flag which signals
|
||||||
|
# us that they processed the message correctly, but have
|
||||||
|
# nothing to reply with, so we avoid replying with a usage string
|
||||||
|
if reply is not messaging.NULL_MESSAGE:
|
||||||
|
LOG.debug("Sending '{}'".format(reply))
|
||||||
|
|
||||||
|
msg = messaging.TextMessage(
|
||||||
|
self.config["aprs"]["login"],
|
||||||
|
fromcall,
|
||||||
|
reply,
|
||||||
|
)
|
||||||
|
self.msg_queues["tx"].put(msg)
|
||||||
|
else:
|
||||||
|
LOG.debug("Got NULL MESSAGE from plugin")
|
||||||
|
|
||||||
if not found_command:
|
if not found_command:
|
||||||
plugins = pm.get_msg_plugins()
|
plugins = pm.get_msg_plugins()
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ footer {
|
|||||||
#center {
|
#center {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
#messageChart, #emailChart, #memChart {
|
#packetsChart, #messageChart, #emailChart, #memChart {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
}
|
}
|
||||||
@@ -74,3 +74,11 @@ footer {
|
|||||||
background-color: lightcoral;
|
background-color: lightcoral;
|
||||||
text-align: left;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
+57
-106
@@ -8,7 +8,10 @@ window.chartColors = {
|
|||||||
blue: 'rgb(54, 162, 235)',
|
blue: 'rgb(54, 162, 235)',
|
||||||
purple: 'rgb(153, 102, 255)',
|
purple: 'rgb(153, 102, 255)',
|
||||||
grey: 'rgb(201, 203, 207)',
|
grey: 'rgb(201, 203, 207)',
|
||||||
black: 'rgb(0, 0, 0)'
|
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 size_dict(d){c=0; for (i in d) ++c; return c}
|
||||||
@@ -20,28 +23,28 @@ function start_charts() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
memory_chart = new Chart($("#memChart"), {
|
packets_chart = new Chart($("#packetsChart"), {
|
||||||
label: 'Memory Usage',
|
label: 'APRS Packets',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: [],
|
labels: [],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Peak Ram usage',
|
label: 'Packets Sent',
|
||||||
borderColor: window.chartColors.red,
|
borderColor: window.chartColors.lightcoral,
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Current Ram usage',
|
label: 'Packets Recieved',
|
||||||
borderColor: window.chartColors.blue,
|
borderColor: window.chartColors.darkseagreen,
|
||||||
data: [],
|
data: [],
|
||||||
}],
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Memory Usage',
|
text: 'APRS Packets',
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
@@ -67,12 +70,12 @@ function start_charts() {
|
|||||||
labels: [],
|
labels: [],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Messages Sent',
|
label: 'Messages Sent',
|
||||||
borderColor: window.chartColors.green,
|
borderColor: window.chartColors.lightcoral,
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Messages Recieved',
|
label: 'Messages Recieved',
|
||||||
borderColor: window.chartColors.yellow,
|
borderColor: window.chartColors.darkseagreen,
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -117,12 +120,12 @@ function start_charts() {
|
|||||||
labels: [],
|
labels: [],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Sent',
|
label: 'Sent',
|
||||||
borderColor: window.chartColors.green,
|
borderColor: window.chartColors.lightcoral,
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Recieved',
|
label: 'Recieved',
|
||||||
borderColor: window.chartColors.yellow,
|
borderColor: window.chartColors.darkseagreen,
|
||||||
data: [],
|
data: [],
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
@@ -149,6 +152,46 @@ function start_charts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -182,100 +225,8 @@ function update_stats( data ) {
|
|||||||
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
|
||||||
$("#jsonstats").html(html_pretty);
|
$("#jsonstats").html(html_pretty);
|
||||||
short_time = data["time"].split(/\s(.+)/)[1];
|
short_time = data["time"].split(/\s(.+)/)[1];
|
||||||
|
updateDualData(packets_chart, short_time, data["stats"]["packets"]["sent"], data["stats"]["packets"]["received"]);
|
||||||
updateQuadData(message_chart, short_time, data["stats"]["messages"]["sent"], data["stats"]["messages"]["recieved"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]);
|
updateQuadData(message_chart, short_time, data["stats"]["messages"]["sent"], data["stats"]["messages"]["recieved"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]);
|
||||||
updateDualData(email_chart, short_time, data["stats"]["email"]["sent"], data["stats"]["email"]["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"]);
|
updateDualData(memory_chart, short_time, data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]);
|
||||||
|
|
||||||
// Update the watch list
|
|
||||||
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(data["stats"]["aprsd"]["watch_list"], function(i, val) {
|
|
||||||
html_str += '<tr><td class="collapsing"><i class="phone volume icon"></i>' + i + '</td><td>' + val["last"] + '</td></tr>'
|
|
||||||
});
|
|
||||||
html_str += "</tbody></table>";
|
|
||||||
watchdiv.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, function(i, val) {
|
|
||||||
if ( packet_list.hasOwnProperty(val["ts"]) == false ) {
|
|
||||||
// Store the packet
|
|
||||||
packet_list[val["ts"]] = val;
|
|
||||||
ts_str = val["ts"].toString();
|
|
||||||
ts = ts_str.split(".")[0]*1000;
|
|
||||||
var d = new Date(ts).toLocaleDateString("en-US")
|
|
||||||
var t = new Date(ts).toLocaleTimeString("en-US")
|
|
||||||
if (val.hasOwnProperty('from') == false) {
|
|
||||||
from = val['fromcall']
|
|
||||||
title_id = 'title_tx'
|
|
||||||
} else {
|
|
||||||
from = val['from']
|
|
||||||
title_id = 'title_rx'
|
|
||||||
}
|
|
||||||
var from_to = d + " " + t + " " + from + " > "
|
|
||||||
|
|
||||||
if (val.hasOwnProperty('addresse')) {
|
|
||||||
from_to = from_to + val['addresse']
|
|
||||||
} else if (val.hasOwnProperty('tocall')) {
|
|
||||||
from_to = from_to + val['tocall']
|
|
||||||
} else if (val.hasOwnProperty('format') && val['format'] == 'mic-e') {
|
|
||||||
from_to = from_to + "Mic-E"
|
|
||||||
}
|
|
||||||
|
|
||||||
from_to = from_to + " - " + val['raw']
|
|
||||||
|
|
||||||
json_pretty = Prism.highlight(JSON.stringify(val, 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);
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// watchlist is a dict of ham callsign => symbol, packets
|
||||||
|
var watchlist = {};
|
||||||
|
|
||||||
|
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
|
||||||
|
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(data["stats"]["aprsd"]["watch_list"], 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_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, function(i, val) {
|
||||||
|
update_watchlist_from_packet(val['from'], val);
|
||||||
|
if ( packet_list.hasOwnProperty(val["ts"]) == false ) {
|
||||||
|
// Store the packet
|
||||||
|
packet_list[val["ts"]] = val;
|
||||||
|
ts_str = val["ts"].toString();
|
||||||
|
ts = ts_str.split(".")[0]*1000;
|
||||||
|
var d = new Date(ts).toLocaleDateString("en-US")
|
||||||
|
var t = new Date(ts).toLocaleTimeString("en-US")
|
||||||
|
if (val.hasOwnProperty('from') == false) {
|
||||||
|
from = val['fromcall']
|
||||||
|
title_id = 'title_tx'
|
||||||
|
} else {
|
||||||
|
from = val['from']
|
||||||
|
title_id = 'title_rx'
|
||||||
|
}
|
||||||
|
var from_to = d + " " + t + " " + from + " > "
|
||||||
|
|
||||||
|
if (val.hasOwnProperty('addresse')) {
|
||||||
|
from_to = from_to + val['addresse']
|
||||||
|
} else if (val.hasOwnProperty('tocall')) {
|
||||||
|
from_to = from_to + val['tocall']
|
||||||
|
} else if (val.hasOwnProperty('format') && val['format'] == 'mic-e') {
|
||||||
|
from_to = from_to + "Mic-E"
|
||||||
|
}
|
||||||
|
|
||||||
|
from_to = from_to + " - " + val['raw']
|
||||||
|
|
||||||
|
json_pretty = Prism.highlight(JSON.stringify(val, 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);
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -12,10 +12,11 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
|
<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>
|
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/css/index.css">
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
<link rel="stylesheet" href="/css/tabs.css">
|
<link rel="stylesheet" href="/static/css/tabs.css">
|
||||||
<script src="/js/charts.js"></script>
|
<script src="/static/js/main.js"></script>
|
||||||
<script src="/js/tabs.js"></script>
|
<script src="/static/js/charts.js"></script>
|
||||||
|
<script src="/static/js/tabs.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript"">
|
<script type="text/javascript"">
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
var cfg_pretty = JSON.stringify(cfg_json, null, '\t');
|
var cfg_pretty = JSON.stringify(cfg_json, null, '\t');
|
||||||
const html_pretty = Prism.highlight( cfg_pretty, Prism.languages.json, 'json');
|
const html_pretty = Prism.highlight( cfg_pretty, Prism.languages.json, 'json');
|
||||||
$("#configjson").html(html_pretty);
|
$("#configjson").html(html_pretty);
|
||||||
|
$("#jsonstats").fadeToggle(1000);
|
||||||
|
|
||||||
$('.ui.accordion').accordion({exclusive: false});
|
$('.ui.accordion').accordion({exclusive: false});
|
||||||
$('.menu .item').tab('change tab', 'charts-tab');
|
$('.menu .item').tab('change tab', 'charts-tab');
|
||||||
@@ -70,22 +72,45 @@
|
|||||||
<div class="item" data-tab="msgs-tab">Messages</div>
|
<div class="item" data-tab="msgs-tab">Messages</div>
|
||||||
<div class="item" data-tab="watch-tab">Watch List</div>
|
<div class="item" data-tab="watch-tab">Watch List</div>
|
||||||
<div class="item" data-tab="config-tab">Config</div>
|
<div class="item" data-tab="config-tab">Config</div>
|
||||||
|
<div class="item" data-tab="raw-tab">Raw JSON</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab content -->
|
<!-- Tab content -->
|
||||||
<div class="ui bottom attached active tab segment" data-tab="charts-tab">
|
<div class="ui bottom attached active tab segment" data-tab="charts-tab">
|
||||||
<h3 class="ui dividing header">Charts</h3>
|
<h3 class="ui dividing header">Charts</h3>
|
||||||
<div id="graphs">
|
<div class="ui equal width relaxed grid">
|
||||||
<div id="left"><canvas id="messageChart"></canvas></div>
|
<div class="row">
|
||||||
<div id="right"><canvas class="right" id="emailChart"></canvas></div>
|
<div class="column">
|
||||||
</div>
|
<div class="ui segment" style="height: 300px">
|
||||||
<div id="graphs_center">
|
<canvas id="packetsChart"></canvas>
|
||||||
<div id="center"><canvas id="memChart"></canvas></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="stats">
|
<div class="column">
|
||||||
<button class="ui button" id="toggleStats">Toggle raw json</button>
|
<div class="ui segment" style="height: 300px">
|
||||||
<pre id="jsonstats" class="language-json">{{ stats }}</pre>
|
<canvas id="messageChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="column">
|
||||||
|
<div class="ui segment" style="height: 300px">
|
||||||
|
<canvas id="emailChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="ui segment" style="height: 300px">
|
||||||
|
<canvas id="memChart"></canvas>
|
||||||
|
</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>
|
</div>
|
||||||
|
|
||||||
<div class="ui bottom attached tab segment" data-tab="msgs-tab">
|
<div class="ui bottom attached tab segment" data-tab="msgs-tab">
|
||||||
@@ -109,6 +134,11 @@
|
|||||||
<pre id="configjson" class="language-json">{{ config_json|safe }}</pre>
|
<pre id="configjson" class="language-json">{{ config_json|safe }}</pre>
|
||||||
</div>
|
</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-json">{{ stats|safe }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ui text container">
|
<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://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>
|
<a href="https://github.com/craigerl/aprsd"><img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" height="18"></a>
|
||||||
|
|||||||
Reference in New Issue
Block a user