1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-08-17 17:13:49 -04:00

Compare commits

..

7 Commits

Author SHA1 Message Date
hemna 691b18fd1c Prep for v2.1.0
Update the Changelog for v2.1.0
2021-08-13 13:11:49 -04:00
hemna 911730b28a Merge pull request #68 from craigerl/plugins_multiple_msgs
Enable multiple replies for plugins
2021-08-13 12:45:52 -04:00
hemna 349250685b Enable multiple replies for plugins
This patch adds the ability for plugins to send multiple messages
back in response to a command/message.  The plugin simple needs
to return a list of messages (Strings).  Each string in that list
will result in a separate message being sent back to the originator
of the message.
2021-08-13 12:36:48 -04:00
hemna 840c8a990e Put in a fix for aprslib parse exceptions
This patch adds a fix for the aprslib consumer function
to ensure that we don't bail when logging a ParseError
2021-08-13 10:31:45 -04:00
hemna ed4995b6eb Fixed time plugin 2021-07-29 20:17:58 -04:00
hemna 6740ff80be Updated the charts Added the packets chart
This patch adds the APRS Packets chart to the charts admin ui.
Also moves the raw json as it's own tab
2021-07-22 20:44:20 -04:00
hemna be8179415a Added showing symbol images to watch list
This patch updates the Admin UI to display the APRS icon symbol
associated with a mic-e packet on the watch list tab for all
entries in the watch list.
2021-07-21 09:21:04 -04:00
16 changed files with 391 additions and 145 deletions
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+9 -1
View File
@@ -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
View File
@@ -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 + "&nbsp;&nbsp;&nbsp;&nbsp;" + 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 + "&nbsp;&nbsp;-&nbsp;&nbsp;" + 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);
}
});
})();
} }
+143
View File
@@ -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 + "&nbsp;&nbsp;&nbsp;&nbsp;" + 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 + "&nbsp;&nbsp;-&nbsp;&nbsp;" + 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);
}
});
})();
}
+44 -14
View File
@@ -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>