Add first server alerts, improve UI a bit.
This commit is contained in:
parent
a3d0635bae
commit
4f21bee47e
6 changed files with 158 additions and 15 deletions
18
alerts/dummy.py
Normal file
18
alerts/dummy.py
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
)]
|
72
alerts/server.py
Normal file
72
alerts/server.py
Normal file
|
@ -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
|
||||||
|
})]
|
|
@ -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
|
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():
|
class AlertManager():
|
||||||
_alerts: List[Alert]
|
_alerts: List[Alert]
|
||||||
|
_providers: List[AlertProvider] = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._alerts = [Alert(
|
self._alerts = []
|
||||||
{
|
|
||||||
"start_time": -1,
|
# Find all the alert providers from the /alerts/ directory.
|
||||||
"id": "test",
|
providers = {
|
||||||
"title": "Test Alert",
|
name: import_module(name)
|
||||||
"description": "This is a test alert.",
|
for _, name, _
|
||||||
"module": "Test",
|
in iter_namespace(alerts)
|
||||||
"severity": CRITICAL
|
|
||||||
}
|
}
|
||||||
)]
|
|
||||||
|
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
|
@property
|
||||||
def alerts_current(self):
|
def alerts_current(self):
|
||||||
|
self.poll_alerts()
|
||||||
return self._alerts
|
return self._alerts
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alert_count_current(self):
|
def alert_count_current(self):
|
||||||
|
self.poll_alerts()
|
||||||
return len(self._alerts)
|
return len(self._alerts)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alert_count_previous(self):
|
def alert_count_previous(self):
|
||||||
|
self.poll_alerts()
|
||||||
return len(self._alerts)
|
return len(self._alerts)
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
Date:
|
Date:
|
||||||
October, November 2020
|
October, November 2020
|
||||||
"""
|
"""
|
||||||
|
from datetime import datetime
|
||||||
from file_manager import FileManager
|
from file_manager import FileManager
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from multiprocessing.queues import Queue
|
from multiprocessing.queues import Queue
|
||||||
|
@ -224,6 +225,7 @@ class BAPSicleServer:
|
||||||
)
|
)
|
||||||
|
|
||||||
self.state.update("running_state", "running")
|
self.state.update("running_state", "running")
|
||||||
|
self.state.update("start_time", datetime.now().timestamp())
|
||||||
|
|
||||||
print("Launching BAPSicle...")
|
print("Launching BAPSicle...")
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<meta http-equiv="refresh" content="2;url=/alerts" />
|
<meta http-equiv="refresh" content="15;url=/alerts" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_inner %}
|
{% block content_inner %}
|
||||||
{% if data %}
|
{% if data %}
|
||||||
|
@ -25,8 +25,10 @@
|
||||||
|
|
||||||
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
|
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<strong>Module: </strong>{{ alert.module }}<br>
|
<strong>Module: </strong><a href="/logs/{{ alert.module }}" title="Click for logs">{{ alert.module }}</a>
|
||||||
{{ alert.description }}
|
{% autoescape false %}
|
||||||
|
<p>{{ alert.description | replace("\n\n", "</p><p>") | replace("\n", "<br/>")}}</p>
|
||||||
|
{% endautoescape %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<meta http-equiv="refresh" content="2;url=/" />
|
<meta http-equiv="refresh" content="60;url=/" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue