diff --git a/alerts/dummy.py b/alerts/dummy.py new file mode 100644 index 0000000..92cd898 --- /dev/null +++ b/alerts/dummy.py @@ -0,0 +1,18 @@ +from helpers.alert_manager import AlertProvider +from package import BETA +from baps_types.alert import WARNING, Alert +# Dummy alert provider for testing basics like UI without needing to actually cause errors. +class DummyAlertProvider(AlertProvider): + + def get_alerts(self): + if BETA: + return [Alert( + { + "start_time": -1, + "id": "test", + "title": "BAPSicle is in Debug Mode", + "description": "This is a test alert. It will not appear on production builds.", + "module": "Test", + "severity": WARNING + } + )] diff --git a/alerts/server.py b/alerts/server.py new file mode 100644 index 0000000..7b56151 --- /dev/null +++ b/alerts/server.py @@ -0,0 +1,72 @@ +# Any alerts produced by the server.py layer. This likely means BIG issues. +import json +from typing import Any, Dict, List +from datetime import datetime, timedelta +from helpers.os_environment import resolve_external_file_path +from helpers.alert_manager import AlertProvider +from baps_types.alert import CRITICAL, WARNING, Alert + +MODULE = "BAPSicleServer" # This should match the log file, so the UI will link to the logs page. + +class ServerAlertProvider(AlertProvider): + + _state: Dict[str, Any] + # To simplify monitoring (and allow detection of things going super weird), we are going to read from the state file to work out the alerts. + def get_alerts(self): + with open(resolve_external_file_path("state/BAPSicleServer.json")) as file: + self._state = json.loads(file.read()) + + + funcs = [self._api_key, self._start_time] + + alerts: List[Alert] = [] + + for func in funcs: + func_alerts = func() + if func_alerts: + alerts.extend(func_alerts) + + return alerts + + def _api_key(self): + if not self._state["myradio_api_key"]: + return [Alert({ + "start_time": -1, # Now + "id": "api_key_missing", + "title": "MyRadio API Key is not configured.", + "description": "This means you will be unable to load show plans, audio items, or tracklist. Please set one on the 'Server Config' page.", + "module": MODULE, + "severity": CRITICAL + })] + + if len(self._state["myradio_api_key"]) < 10: + return [Alert({ + "start_time": -1, + "id": "api_key_missing", + "title": "MyRadio API Key seems incorrect.", + "description": "The API key is less than 10 characters, it's probably not a valid one. If it is valid, it shouldn't be.", + "module": MODULE, + "severity": WARNING + })] + + def _start_time(self): + start_time = self._state["start_time"] + start_time = datetime.fromtimestamp(start_time) + delta = timedelta( + days=1, + ) + if (start_time + delta > datetime.now()): + return [Alert({ + "start_time": -1, + "id": "server_restarted", + "title": "BAPSicle restarted recently.", + "description": +"""The BAPSicle server restarted at {}, less than a day ago. + +It may have been automatically restarted by the OS. + +If this is not expected, please check logs to investigate why BAPSicle restarted/crashed.""" + .format(str(start_time).rsplit(".",1)[0]), + "module": MODULE, + "severity": WARNING + })] diff --git a/helpers/alert_manager.py b/helpers/alert_manager.py index 820f45d..fea93ed 100644 --- a/helpers/alert_manager.py +++ b/helpers/alert_manager.py @@ -1,29 +1,78 @@ -from typing import List +from typing import Any, List + +#Magic for importing alert providers from alerts directory. +from pkgutil import iter_modules +from importlib import import_module +from inspect import getmembers,isclass +from sys import modules + from baps_types.alert import CRITICAL, Alert +import alerts + +def iter_namespace(ns_pkg): + # Specifying the second argument (prefix) to iter_modules makes the + # returned name an absolute name instead of a relative one. This allows + # import_module to work without having to do additional modification to + # the name. + return iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".") + + +class AlertProvider(): + + def __init__(self): + return None + + def get_alerts(self): + return [] class AlertManager(): _alerts: List[Alert] + _providers: List[AlertProvider] = [] def __init__(self): - self._alerts = [Alert( - { - "start_time": -1, - "id": "test", - "title": "Test Alert", - "description": "This is a test alert.", - "module": "Test", - "severity": CRITICAL - } - )] + self._alerts = [] + + # Find all the alert providers from the /alerts/ directory. + providers = { + name: import_module(name) + for _, name, _ + in iter_namespace(alerts) + } + + for provider in providers: + classes: List[Any] = [mem[1] for mem in getmembers(modules[provider], isclass) if mem[1].__module__ == modules[provider].__name__] + + if (len(classes) != 1): + print(classes) + raise Exception("Can't import plugin " + provider + " because it doesn't have 1 class.") + + self._providers.append(classes[0]()) + + + print("Discovered alert providers: ", self._providers) + + def poll_alerts(self): + + # Poll modules for any alerts. + alerts: List[Alert] = [] + for provider in self._providers: + provider_alerts = provider.get_alerts() + if provider_alerts: + alerts.extend(provider_alerts) + + self._alerts = alerts @property def alerts_current(self): + self.poll_alerts() return self._alerts @property def alert_count_current(self): + self.poll_alerts() return len(self._alerts) @property def alert_count_previous(self): + self.poll_alerts() return len(self._alerts) diff --git a/server.py b/server.py index 83a80d0..82dde6e 100644 --- a/server.py +++ b/server.py @@ -12,6 +12,7 @@ Date: October, November 2020 """ +from datetime import datetime from file_manager import FileManager import multiprocessing from multiprocessing.queues import Queue @@ -224,6 +225,7 @@ class BAPSicleServer: ) self.state.update("running_state", "running") + self.state.update("start_time", datetime.now().timestamp()) print("Launching BAPSicle...") diff --git a/ui-templates/alerts.html b/ui-templates/alerts.html index edf1b7e..ff100c0 100644 --- a/ui-templates/alerts.html +++ b/ui-templates/alerts.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} {% block head %} - + {% endblock %} {% block content_inner %} {% if data %} @@ -25,8 +25,10 @@
- Module: {{ alert.module }}
- {{ alert.description }} + Module: {{ alert.module }} + {% autoescape false %} +

{{ alert.description | replace("\n\n", "

") | replace("\n", "
")}}

+ {% endautoescape %}
diff --git a/ui-templates/index.html b/ui-templates/index.html index bb8b21b..fe62487 100644 --- a/ui-templates/index.html +++ b/ui-templates/index.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} {% block head %} - + {% endblock %} {% block content %}