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
|
||||
=======
|
||||
|
||||
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
|
||||
------
|
||||
|
||||
* Updated docs for 2.0.0
|
||||
* Reworked the notification threads and admin ui
|
||||
* Fixed small bug with packets get\_packet\_type
|
||||
* Updated overview images
|
||||
|
||||
+13
-3
@@ -224,11 +224,21 @@ class Aprsdis(aprslib.IS):
|
||||
self.logger.debug("Server: %s", line.decode("utf8"))
|
||||
stats.APRSDStats().set_aprsis_keepalive()
|
||||
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:
|
||||
self.logger.log(9, "unknown format %s", exp.args)
|
||||
self.logger.log(
|
||||
9,
|
||||
"%s\n Packet: %s",
|
||||
exp,
|
||||
exp.packet,
|
||||
)
|
||||
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):
|
||||
raise
|
||||
except (ConnectionDrop, ConnectionError):
|
||||
|
||||
+8
-1
@@ -121,6 +121,13 @@ class APRSDFlask(flask_classful.FlaskView):
|
||||
}
|
||||
|
||||
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 = {
|
||||
"time": now.strftime(time_format),
|
||||
@@ -170,7 +177,7 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
||||
def init_flask(config, loglevel, quiet):
|
||||
flask_app = flask.Flask(
|
||||
"aprsd",
|
||||
static_url_path="",
|
||||
static_url_path="/static",
|
||||
static_folder="web/static",
|
||||
template_folder="web/templates",
|
||||
)
|
||||
|
||||
+13
-1
@@ -32,7 +32,18 @@ import time
|
||||
|
||||
# local imports here
|
||||
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
|
||||
from aprslib.exceptions import LoginError
|
||||
import click
|
||||
@@ -519,6 +530,7 @@ def server(
|
||||
"enabled",
|
||||
True,
|
||||
):
|
||||
packets.PacketList(config)
|
||||
notify_thread = threads.APRSDNotifyThread(
|
||||
msg_queues=msg_queues,
|
||||
config=config,
|
||||
|
||||
@@ -16,9 +16,13 @@ class PacketList:
|
||||
"""Class to track all of the packets rx'd and tx'd by aprsd."""
|
||||
|
||||
_instance = None
|
||||
config = None
|
||||
|
||||
packet_list = {}
|
||||
|
||||
total_recv = 0
|
||||
total_tx = 0
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
@@ -26,6 +30,10 @@ class PacketList:
|
||||
cls._instance.lock = threading.Lock()
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, config=None):
|
||||
if config:
|
||||
self.config = config
|
||||
|
||||
def __iter__(self):
|
||||
with self.lock:
|
||||
return iter(self.packet_list)
|
||||
@@ -33,12 +41,22 @@ class PacketList:
|
||||
def add(self, packet):
|
||||
with self.lock:
|
||||
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)
|
||||
|
||||
def get(self):
|
||||
with self.lock:
|
||||
return self.packet_list.get()
|
||||
|
||||
def total_received(self):
|
||||
return self.total_recv
|
||||
|
||||
def total_sent(self):
|
||||
return self.total_tx
|
||||
|
||||
|
||||
class WatchList:
|
||||
"""Global watch list and info for callsigns."""
|
||||
|
||||
+49
-6
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
from aprsd import fuzzyclock, plugin, plugin_utils, trace, utils
|
||||
@@ -56,17 +57,38 @@ class TimeOpenCageDataPlugin(TimePlugin):
|
||||
@trace.trace
|
||||
def command(self, packet):
|
||||
fromcall = packet.get("from")
|
||||
# message = packet.get("message_text", None)
|
||||
message = packet.get("message_text", None)
|
||||
# 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:
|
||||
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:
|
||||
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
||||
return "Failed to fetch location"
|
||||
|
||||
# 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"]
|
||||
lon = aprs_data["entries"][0]["lng"]
|
||||
|
||||
@@ -101,16 +123,37 @@ class TimeOWMPlugin(TimePlugin):
|
||||
@trace.trace
|
||||
def command(self, packet):
|
||||
fromcall = packet.get("from")
|
||||
# message = packet.get("message_text", None)
|
||||
message = packet.get("message_text", None)
|
||||
# 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"]
|
||||
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:
|
||||
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
||||
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"]
|
||||
lon = aprs_data["entries"][0]["lng"]
|
||||
|
||||
|
||||
+27
-13
@@ -253,21 +253,35 @@ class APRSDRXThread(APRSDThread):
|
||||
try:
|
||||
results = pm.run(packet)
|
||||
for reply in results:
|
||||
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))
|
||||
if isinstance(reply, list):
|
||||
# one of the plugins wants to send multiple messages
|
||||
found_command = True
|
||||
for subreply in reply:
|
||||
LOG.debug("Sending '{}'".format(subreply))
|
||||
|
||||
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:
|
||||
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:
|
||||
plugins = pm.get_msg_plugins()
|
||||
|
||||
@@ -38,7 +38,7 @@ footer {
|
||||
#center {
|
||||
height: 300px;
|
||||
}
|
||||
#messageChart, #emailChart, #memChart {
|
||||
#packetsChart, #messageChart, #emailChart, #memChart {
|
||||
border: 1px solid #ccc;
|
||||
background: #ddd;
|
||||
}
|
||||
@@ -74,3 +74,11 @@ footer {
|
||||
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;
|
||||
}
|
||||
|
||||
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)',
|
||||
purple: 'rgb(153, 102, 255)',
|
||||
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}
|
||||
@@ -20,28 +23,28 @@ function start_charts() {
|
||||
}
|
||||
});
|
||||
|
||||
memory_chart = new Chart($("#memChart"), {
|
||||
label: 'Memory Usage',
|
||||
packets_chart = new Chart($("#packetsChart"), {
|
||||
label: 'APRS Packets',
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Peak Ram usage',
|
||||
borderColor: window.chartColors.red,
|
||||
label: 'Packets Sent',
|
||||
borderColor: window.chartColors.lightcoral,
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
label: 'Current Ram usage',
|
||||
borderColor: window.chartColors.blue,
|
||||
label: 'Packets Recieved',
|
||||
borderColor: window.chartColors.darkseagreen,
|
||||
data: [],
|
||||
}],
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Memory Usage',
|
||||
text: 'APRS Packets',
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
@@ -67,12 +70,12 @@ function start_charts() {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Messages Sent',
|
||||
borderColor: window.chartColors.green,
|
||||
borderColor: window.chartColors.lightcoral,
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
label: 'Messages Recieved',
|
||||
borderColor: window.chartColors.yellow,
|
||||
borderColor: window.chartColors.darkseagreen,
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
@@ -117,12 +120,12 @@ function start_charts() {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Sent',
|
||||
borderColor: window.chartColors.green,
|
||||
borderColor: window.chartColors.lightcoral,
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
label: 'Recieved',
|
||||
borderColor: window.chartColors.yellow,
|
||||
borderColor: window.chartColors.darkseagreen,
|
||||
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');
|
||||
$("#jsonstats").html(html_pretty);
|
||||
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"]);
|
||||
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"]);
|
||||
|
||||
// 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">
|
||||
<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="/css/tabs.css">
|
||||
<script src="/js/charts.js"></script>
|
||||
<script src="/js/tabs.js"></script>
|
||||
<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/charts.js"></script>
|
||||
<script src="/static/js/tabs.js"></script>
|
||||
|
||||
|
||||
<script type="text/javascript"">
|
||||
@@ -40,6 +41,7 @@
|
||||
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);
|
||||
|
||||
$('.ui.accordion').accordion({exclusive: false});
|
||||
$('.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="watch-tab">Watch List</div>
|
||||
<div class="item" data-tab="config-tab">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 id="graphs">
|
||||
<div id="left"><canvas id="messageChart"></canvas></div>
|
||||
<div id="right"><canvas class="right" id="emailChart"></canvas></div>
|
||||
</div>
|
||||
<div id="graphs_center">
|
||||
<div id="center"><canvas id="memChart"></canvas></div>
|
||||
</div>
|
||||
<div id="stats">
|
||||
<button class="ui button" id="toggleStats">Toggle raw json</button>
|
||||
<pre id="jsonstats" class="language-json">{{ stats }}</pre>
|
||||
<div class="ui equal width relaxed grid">
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="ui segment" style="height: 300px">
|
||||
<canvas id="packetsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="ui segment" style="height: 300px">
|
||||
<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 class="ui bottom attached tab segment" data-tab="msgs-tab">
|
||||
@@ -109,6 +134,11 @@
|
||||
<pre id="configjson" class="language-json">{{ config_json|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-json">{{ 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>
|
||||
|
||||
Reference in New Issue
Block a user