From f211e5cabbc5735ba3bdd1fd0b538403e0b8ee99 Mon Sep 17 00:00:00 2001 From: Hemna Date: Wed, 24 Mar 2021 10:45:03 -0400 Subject: [PATCH 01/11] Added aprsd web index page This patch adds an index page for the flask web server that users can hit at / --- aprsd/flask.py | 5 ++-- aprsd/web/templates/index.html | 42 +++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/aprsd/flask.py b/aprsd/flask.py index d6136b2..f0dbf39 100644 --- a/aprsd/flask.py +++ b/aprsd/flask.py @@ -40,8 +40,7 @@ class APRSDFlask(flask_classful.FlaskView): users = self.users def index(self): - return "Hello" - # return flask.render_template("index.html", message=msg) + return flask.render_template("index.html", message=self.stats()) @auth.login_required def messages(self): @@ -89,7 +88,7 @@ def init_flask(config): ) server = APRSDFlask() server.set_config(config) - # flask_app.route('/', methods=['GET'])(server.index) + flask_app.route("/", methods=["GET"])(server.index) flask_app.route("/stats", methods=["GET"])(server.stats) flask_app.route("/messages", methods=["GET"])(server.messages) flask_app.route("/save", methods=["GET"])(server.save) diff --git a/aprsd/web/templates/index.html b/aprsd/web/templates/index.html index dc09bc0..d016f1c 100644 --- a/aprsd/web/templates/index.html +++ b/aprsd/web/templates/index.html @@ -1,4 +1,44 @@ + + -

{{ message }}

+ + + + + + + + + + +
{{ stats }}
+ + From c7d10f53a36f84304190dea2c7154f827b2cf479 Mon Sep 17 00:00:00 2001 From: Hemna Date: Wed, 24 Mar 2021 16:07:09 -0400 Subject: [PATCH 02/11] Updated web stats index to show messages and ram usage This patch updates the main index page to show both the graph of tx/rx messages as well as peak/current ram usage. --- README.rst | 3 + aprsd/flask.py | 7 ++ aprsd/stats.py | 28 ++++- aprsd/threads.py | 2 + aprsd/web/templates/index.html | 222 +++++++++++++++++++++++++++++++-- 5 files changed, 254 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 0401272..f7ea08e 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,9 @@ APRSD .. image:: https://badge.fury.io/py/aprsd.svg :target: https://badge.fury.io/py/aprsd +.. image:: http://hits.dwyl.com/craigerl/aprsd.svg + :target: http://hits.dwyl.com/craigerl/aprsd + .. image:: https://github.com/craigerl/aprsd/workflows/python/badge.svg :target: https://github.com/craigerl/aprsd/actions diff --git a/aprsd/flask.py b/aprsd/flask.py index f0dbf39..e9b51ca 100644 --- a/aprsd/flask.py +++ b/aprsd/flask.py @@ -1,5 +1,7 @@ +import datetime import json import logging +import tracemalloc import aprsd from aprsd import messaging, plugin, stats @@ -69,12 +71,17 @@ class APRSDFlask(flask_classful.FlaskView): def stats(self): stats_obj = stats.APRSDStats() track = messaging.MsgTrack() + now = datetime.datetime.now() + current, peak = tracemalloc.get_traced_memory() result = { "version": aprsd.__version__, "uptime": stats_obj.uptime, "size_tracker": len(track), "stats": stats_obj.stats(), + "time": now.strftime("%m-%d-%Y %H:%M:%S"), + "memory_current": current, + "memory_peak": peak, } return json.dumps(result) diff --git a/aprsd/stats.py b/aprsd/stats.py index 30c9f5a..986a795 100644 --- a/aprsd/stats.py +++ b/aprsd/stats.py @@ -26,6 +26,9 @@ class APRSDStats: _email_tx = 0 _email_rx = 0 + _mem_current = 0 + _mem_peak = 0 + def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) @@ -43,6 +46,24 @@ class APRSDStats: with self.lock: return str(datetime.datetime.now() - self.start_time) + @property + def memory(self): + with self.lock: + return self._mem_current + + def set_memory(self, memory): + with self.lock: + self._mem_curent = memory + + @property + def memory_peak(self): + with self.lock: + return self._mem_peak + + def set_memory_peak(self, memory): + with self.lock: + self._mem_peak = memory + @property def msgs_tx(self): with self.lock: @@ -126,6 +147,11 @@ class APRSDStats: def stats(self): now = datetime.datetime.now() + if self._email_thread_last_time: + last_update = str(now - self._email_thread_last_time) + else: + last_update = "never" + stats = { "messages": { "tracked": self.msgs_tracked, @@ -138,7 +164,7 @@ class APRSDStats: "email": { "sent": self._email_tx, "recieved": self._email_rx, - "thread_last_update": str(now - self._email_thread_last_time), + "thread_last_update": last_update, }, } return stats diff --git a/aprsd/threads.py b/aprsd/threads.py index 736a052..03b4fc7 100644 --- a/aprsd/threads.py +++ b/aprsd/threads.py @@ -84,6 +84,8 @@ class KeepAliveThread(APRSDThread): email_thread_time = "N/A" current, peak = tracemalloc.get_traced_memory() + stats_obj.set_memory(current) + stats_obj.set_memory_peak(peak) LOG.debug( "Uptime ({}) Tracker({}) " "Msgs: TX:{} RX:{} EmailThread: {} RAM: Current:{} Peak:{}".format( diff --git a/aprsd/web/templates/index.html b/aprsd/web/templates/index.html index d016f1c..68cc58b 100644 --- a/aprsd/web/templates/index.html +++ b/aprsd/web/templates/index.html @@ -1,14 +1,133 @@ + + + + + -
{{ stats }}
- +
+
APRSD version
+
+
+ +
+
+
+ +
+ +
+ +
{{ stats }}
+
+
+ +
+ PyPI version + +
From f10372b320ce5ce8403cee849b769ece6f9d4617 Mon Sep 17 00:00:00 2001 From: Hemna Date: Fri, 26 Mar 2021 11:13:32 -0400 Subject: [PATCH 03/11] Added acks with messages graphs --- aprsd/web/templates/index.html | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/aprsd/web/templates/index.html b/aprsd/web/templates/index.html index 68cc58b..aba197d 100644 --- a/aprsd/web/templates/index.html +++ b/aprsd/web/templates/index.html @@ -23,7 +23,8 @@ green: 'rgb(26, 181, 77)', blue: 'rgb(54, 162, 235)', purple: 'rgb(153, 102, 255)', - grey: 'rgb(201, 203, 207)' + grey: 'rgb(201, 203, 207)', + black: 'rgb(0, 0, 0)' }; function start_charts() { @@ -77,6 +78,16 @@ label: 'Messages Recieved', borderColor: window.chartColors.yellow, data: [], + }, + { + label: 'Ack Sent', + borderColor: window.chartColors.purple, + data: [], + }, + { + label: 'Ack Recieved', + borderColor: window.chartColors.black, + data: [], }], }, options: { @@ -116,6 +127,14 @@ 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 ) { $("#version").text( data["version"] ); @@ -125,7 +144,7 @@ //$("#jsonstats").effect("highlight", {color: "#333333"}, 800); //console.log(data); updateDualData(memory_chart, data["time"], data["memory_peak"], data["memory_current"]); - updateDualData(message_chart, data["time"], data["stats"]["messages"]["sent"], data["stats"]["messages"]["recieved"]); + updateQuadData(message_chart, data["time"], data["stats"]["messages"]["sent"], data["stats"]["messages"]["recieved"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]); } function start_update() { From 6297ebeb670d1ce986795f55ab15750da8ae0f44 Mon Sep 17 00:00:00 2001 From: Hemna Date: Tue, 30 Mar 2021 09:55:14 -0400 Subject: [PATCH 04/11] Make the index page behind auth This patch makes the index page ask for login/password in order to see the stats. --- aprsd/flask.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aprsd/flask.py b/aprsd/flask.py index e9b51ca..4898742 100644 --- a/aprsd/flask.py +++ b/aprsd/flask.py @@ -41,6 +41,7 @@ class APRSDFlask(flask_classful.FlaskView): users = self.users + @auth.login_required def index(self): return flask.render_template("index.html", message=self.stats()) From fb979eda9427355c0fc8d1d981643c9adc799e40 Mon Sep 17 00:00:00 2001 From: Hemna Date: Tue, 30 Mar 2021 10:18:56 -0400 Subject: [PATCH 05/11] Provide an initial datapoint on rendering index This patch adds a single data point when rendering the initial stats for the index page. --- aprsd/flask.py | 11 ++++++++--- aprsd/web/templates/index.html | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/aprsd/flask.py b/aprsd/flask.py index 4898742..1b5db92 100644 --- a/aprsd/flask.py +++ b/aprsd/flask.py @@ -43,7 +43,8 @@ class APRSDFlask(flask_classful.FlaskView): @auth.login_required def index(self): - return flask.render_template("index.html", message=self.stats()) + stats = self._stats() + return flask.render_template("index.html", initial_stats=stats) @auth.login_required def messages(self): @@ -69,7 +70,7 @@ class APRSDFlask(flask_classful.FlaskView): track.save() return json.dumps({"messages": "saved"}) - def stats(self): + def _stats(self): stats_obj = stats.APRSDStats() track = messaging.MsgTrack() now = datetime.datetime.now() @@ -84,7 +85,11 @@ class APRSDFlask(flask_classful.FlaskView): "memory_current": current, "memory_peak": peak, } - return json.dumps(result) + + return result + + def stats(self): + return json.dumps(self._stats()) def init_flask(config): diff --git a/aprsd/web/templates/index.html b/aprsd/web/templates/index.html index aba197d..96d851a 100644 --- a/aprsd/web/templates/index.html +++ b/aprsd/web/templates/index.html @@ -12,6 +12,8 @@ - + #graphs { + display: grid; + width: 100%; + grid-template-columns: 1fr 1fr; + } + #left { + border: 1px solid black; + margin: 2px; + } + #right { + border: 1px solid black; + margin: 2px; + } + #stats { + margin: auto; + width: 80%; + } + #jsonstats { + display: none; + } + #title { + font-size: 4em; + } + #uptime, #aprsis { + font-size: 1em; + } + #callsign { + font-size: 1.4em; + color: #00F; + } +
APRSD version
+
{{ callsign }}
From bf8d2c6088d9c09783b8d68de4c88fe77fbbc9f5 Mon Sep 17 00:00:00 2001 From: Hemna Date: Thu, 1 Apr 2021 23:12:25 -0400 Subject: [PATCH 09/11] Reworked the stats dict output and healthcheck This patch reworks the stats object dict and includes more data. Also includes aprsis last update timestamp (from last recieved message). This is used to help determine if the aprsis server connection is still alive and well. --- aprsd/client.py | 71 ++++++++++++++++++++++++++++++++-- aprsd/flask.py | 21 ++++------ aprsd/healthcheck.py | 9 +++++ aprsd/stats.py | 43 +++++++++++++++++++- aprsd/threads.py | 16 +++++--- aprsd/utils.py | 7 ++++ aprsd/web/templates/index.html | 8 ++-- 7 files changed, 148 insertions(+), 27 deletions(-) diff --git a/aprsd/client.py b/aprsd/client.py index 6dea8e5..92e9c98 100644 --- a/aprsd/client.py +++ b/aprsd/client.py @@ -3,9 +3,17 @@ import select import time import aprsd +from aprsd import stats import aprslib from aprslib import is_py3 -from aprslib.exceptions import LoginError +from aprslib.exceptions import ( + ConnectionDrop, + ConnectionError, + GenericError, + LoginError, + ParseError, + UnknownFormat, +) LOG = logging.getLogger("APRSD") @@ -163,6 +171,7 @@ class Aprsdis(aprslib.IS): self.logger.info("Connected to {}".format(server_string)) self.server_string = server_string + stats.APRSDStats().set_aprsis_server(server_string) if callsign == "": raise LoginError("Server responded with empty callsign???") @@ -180,11 +189,67 @@ class Aprsdis(aprslib.IS): self.logger.error(str(e)) self.close() raise - except Exception: + except Exception as e: self.close() - self.logger.error("Failed to login") + self.logger.error("Failed to login '{}'".format(e)) raise LoginError("Failed to login") + def consumer(self, callback, blocking=True, immortal=False, raw=False): + """ + When a position sentence is received, it will be passed to the callback function + + blocking: if true (default), runs forever, otherwise will return after one sentence + You can still exit the loop, by raising StopIteration in the callback function + + immortal: When true, consumer will try to reconnect and stop propagation of Parse exceptions + if false (default), consumer will return + + raw: when true, raw packet is passed to callback, otherwise the result from aprs.parse() + """ + + if not self._connected: + raise ConnectionError("not connected to a server") + + line = b"" + + while True: + try: + for line in self._socket_readlines(blocking): + if line[0:1] != b"#": + if raw: + callback(line) + else: + callback(self._parse(line)) + else: + 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]) + except UnknownFormat as exp: + self.logger.log(9, "%s\n Packet: %s", exp.args[0], exp.args[1]) + except LoginError as exp: + self.logger.error("%s: %s", exp.__class__.__name__, exp.args[0]) + except (KeyboardInterrupt, SystemExit): + raise + except (ConnectionDrop, ConnectionError): + self.close() + + if not immortal: + raise + else: + self.connect(blocking=blocking) + continue + except GenericError: + pass + except StopIteration: + break + except Exception: + self.logger.error("APRS Packet: %s", line) + raise + + if not blocking: + break + def get_client(): cl = Client() diff --git a/aprsd/flask.py b/aprsd/flask.py index a1bdb00..2f0eee6 100644 --- a/aprsd/flask.py +++ b/aprsd/flask.py @@ -4,10 +4,8 @@ import logging from logging import NullHandler from logging.handlers import RotatingFileHandler import sys -import tracemalloc -import aprsd -from aprsd import client, messaging, plugin, stats, utils +from aprsd import messaging, plugin, stats, utils import flask import flask_classful from flask_httpauth import HTTPBasicAuth @@ -81,20 +79,15 @@ class APRSDFlask(flask_classful.FlaskView): stats_obj = stats.APRSDStats() track = messaging.MsgTrack() now = datetime.datetime.now() - current, peak = tracemalloc.get_traced_memory() - cl = client.Client() - server_string = cl.client.server_string + + time_format = "%m-%d-%Y %H:%M:%S" + + stats_dict = stats_obj.stats() result = { - "version": aprsd.__version__, - "aprsis_server": server_string, - "callsign": self.config["aprs"]["login"], - "uptime": stats_obj.uptime, + "time": now.strftime(time_format), "size_tracker": len(track), - "stats": stats_obj.stats(), - "time": now.strftime("%m-%d-%Y %H:%M:%S"), - "memory_current": current, - "memory_peak": peak, + "stats": stats_dict, } return result diff --git a/aprsd/healthcheck.py b/aprsd/healthcheck.py index db2f97e..35e3a6e 100644 --- a/aprsd/healthcheck.py +++ b/aprsd/healthcheck.py @@ -215,6 +215,15 @@ def check(loglevel, config_file, health_url, timeout): LOG.error("Email thread is very old! {}".format(d)) sys.exit(-1) + aprsis_last_update = stats["stats"]["aprs-is"]["last_update"] + delta = parse_delta_str(aprsis_last_update) + d = datetime.timedelta(**delta) + max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0} + max_delta = datetime.timedelta(**max_timeout) + if d > max_delta: + LOG.error("APRS-IS last update is very old! {}".format(d)) + sys.exit(-1) + sys.exit(0) diff --git a/aprsd/stats.py b/aprsd/stats.py index 4b4ce1e..6729564 100644 --- a/aprsd/stats.py +++ b/aprsd/stats.py @@ -2,6 +2,9 @@ import datetime import logging import threading +import aprsd +from aprsd import utils + LOG = logging.getLogger("APRSD") @@ -12,6 +15,7 @@ class APRSDStats: config = None start_time = None + _aprsis_keepalive = None _msgs_tracked = 0 _msgs_tx = 0 @@ -35,6 +39,7 @@ class APRSDStats: # any initializetion here cls._instance.lock = threading.Lock() cls._instance.start_time = datetime.datetime.now() + cls._instance._aprsis_keepalive = datetime.datetime.now() return cls._instance def __init__(self, config=None): @@ -53,7 +58,7 @@ class APRSDStats: def set_memory(self, memory): with self.lock: - self._mem_curent = memory + self._mem_current = memory @property def memory_peak(self): @@ -64,6 +69,24 @@ class APRSDStats: with self.lock: self._mem_peak = memory + @property + def aprsis_server(self): + with self.lock: + return self._aprsis_server + + def set_aprsis_server(self, server): + with self.lock: + self._aprsis_server = server + + @property + def aprsis_keepalive(self): + with self.lock: + return self._aprsis_keepalive + + def set_aprsis_keepalive(self): + with self.lock: + self._aprsis_keepalive = datetime.datetime.now() + @property def msgs_tx(self): with self.lock: @@ -152,7 +175,25 @@ class APRSDStats: else: last_update = "never" + if self._aprsis_keepalive: + last_aprsis_keepalive = str(now - self._aprsis_keepalive) + else: + last_aprsis_keepalive = "never" + stats = { + "aprsd": { + "version": aprsd.__version__, + "uptime": self.uptime, + "memory_current": self.memory, + "memory_current_str": utils.human_size(self.memory), + "memory_peak": self.memory_peak, + "memory_peak_str": utils.human_size(self.memory_peak), + }, + "aprs-is": { + "server": self.aprsis_server, + "callsign": self.config["aprs"]["login"], + "last_update": last_aprsis_keepalive, + }, "messages": { "tracked": self.msgs_tracked, "sent": self.msgs_tx, diff --git a/aprsd/threads.py b/aprsd/threads.py index 03b4fc7..01439ee 100644 --- a/aprsd/threads.py +++ b/aprsd/threads.py @@ -1,12 +1,13 @@ import abc import datetime +import gc import logging import queue import threading import time import tracemalloc -from aprsd import client, messaging, plugin, stats, trace +from aprsd import client, messaging, plugin, stats, trace, utils import aprslib LOG = logging.getLogger("APRSD") @@ -74,28 +75,33 @@ class KeepAliveThread(APRSDThread): def loop(self): if self.cntr % 6 == 0: + nuked = gc.collect() tracker = messaging.MsgTrack() stats_obj = stats.APRSDStats() now = datetime.datetime.now() - last_email = stats.APRSDStats().email_thread_time + last_email = stats_obj.email_thread_time if last_email: email_thread_time = str(now - last_email) else: email_thread_time = "N/A" + last_msg_time = str(now - stats_obj.aprsis_keepalive) + current, peak = tracemalloc.get_traced_memory() stats_obj.set_memory(current) stats_obj.set_memory_peak(peak) LOG.debug( "Uptime ({}) Tracker({}) " - "Msgs: TX:{} RX:{} EmailThread: {} RAM: Current:{} Peak:{}".format( + "Msgs: TX:{} RX:{} Last: {} - EmailThread: {} - RAM: Current:{} Peak:{} Nuked: {}".format( stats_obj.uptime, len(tracker), stats_obj.msgs_tx, stats_obj.msgs_rx, + last_msg_time, email_thread_time, - current, - peak, + utils.human_size(current), + utils.human_size(peak), + nuked, ), ) self.cntr += 1 diff --git a/aprsd/utils.py b/aprsd/utils.py index 0be68f9..5373fa0 100644 --- a/aprsd/utils.py +++ b/aprsd/utils.py @@ -361,3 +361,10 @@ def parse_config(config_file): ) return config + + +def human_size(bytes, units=None): + """ Returns a human readable string representation of bytes """ + if not units: + units = [" bytes", "KB", "MB", "GB", "TB", "PB", "EB"] + return str(bytes) + units[0] if bytes < 1024 else human_size(bytes >> 10, units[1:]) diff --git a/aprsd/web/templates/index.html b/aprsd/web/templates/index.html index e4263b8..614d6a4 100644 --- a/aprsd/web/templates/index.html +++ b/aprsd/web/templates/index.html @@ -139,14 +139,14 @@ } function update_stats( data ) { - $("#version").text( data["version"] ); - $("#aprsis").text( "APRS-IS Server: " + data["aprsis_server"] ); - $("#uptime").text( "uptime: " + data["uptime"] ); + $("#version").text( data["stats"]["aprsd"]["version"] ); + $("#aprsis").text( "APRS-IS Server: " + data["stats"]["aprs-is"]["server"] ); + $("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] ); const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json'); $("#jsonstats").html(html_pretty); //$("#jsonstats").effect("highlight", {color: "#333333"}, 800); //console.log(data); - updateDualData(memory_chart, data["time"], data["memory_peak"], data["memory_current"]); + updateDualData(memory_chart, data["time"], data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]); updateQuadData(message_chart, data["time"], data["stats"]["messages"]["sent"], data["stats"]["messages"]["recieved"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]); } From d6806c429ce1de162e4fbc9dab311bdb63765795 Mon Sep 17 00:00:00 2001 From: Hemna Date: Fri, 2 Apr 2021 11:47:52 -0400 Subject: [PATCH 10/11] Added email messages graphs This patch cleans up the layout of the admin web page stats graphs as well as adds in the email stats. Added the titles to each graph, so you know what you are looking at. --- aprsd/web/templates/index.html | 114 ++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 30 deletions(-) diff --git a/aprsd/web/templates/index.html b/aprsd/web/templates/index.html index 614d6a4..7c59d8c 100644 --- a/aprsd/web/templates/index.html +++ b/aprsd/web/templates/index.html @@ -48,7 +48,11 @@ }, options: { responsive: true, - maintainAspectRation: false, + maintainAspectRatio: false, + title: { + display: true, + text: 'Memory Usage', + }, scales: { x: { type: 'timeseries', @@ -94,7 +98,11 @@ }, options: { responsive: true, - maintainAspectRation: false, + maintainAspectRatio: false, + title: { + display: true, + text: 'APRS Messages', + }, scales: { x: { type: 'timeseries', @@ -112,6 +120,45 @@ } }); + email_chart = new Chart($("#emailChart"), { + label: 'Email Messages', + type: 'line', + data: { + labels: [], + datasets: [{ + label: 'Sent', + borderColor: window.chartColors.green, + data: [], + }, + { + label: 'Recieved', + borderColor: window.chartColors.yellow, + 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, + } + } + } + } + }); } @@ -144,10 +191,10 @@ $("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] ); const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json'); $("#jsonstats").html(html_pretty); - //$("#jsonstats").effect("highlight", {color: "#333333"}, 800); - //console.log(data); - updateDualData(memory_chart, data["time"], data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]); - updateQuadData(message_chart, data["time"], data["stats"]["messages"]["sent"], data["stats"]["messages"]["recieved"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]); + short_time = data["time"].split(/\s(.+)/)[1]; + 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"]); } function start_update() { @@ -192,41 +239,45 @@ } header { - padding: 2rem; + padding: 2em; + height: 10vh; } - main { - padding: 0px; + #main { + padding: 2em; + height: 80vh; } - - footer, .push { - padding: 2rem; + footer { + padding: 2em; text-align: center; - } - - #messageChart { - border: 1px solid #ccc; - background: #ddd; height: 10vh; } - #memChart { - border: 1px solid #ccc; - background: #ddd; - height: 10vh; - } - #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 { - border: 1px solid black; - margin: 2px; + margin-right: 2px; + height: 300px; } #right { - border: 1px solid black; - margin: 2px; + height: 300px; + } + #center { + height: 300px; + } + #messageChart, #emailChart, #memChart { + border: 1px solid #ccc; + background: #ddd; } #stats { margin: auto; @@ -257,17 +308,20 @@
-
+
- + +
+
+
{{ stats }}
-
+