mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Starting to implement alerts #17
This commit is contained in:
@@ -2,7 +2,6 @@ import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Thread
|
||||
from types import SimpleNamespace
|
||||
|
||||
import bottle
|
||||
import pytz
|
||||
@@ -30,11 +29,13 @@ class WebServer:
|
||||
|
||||
# Routes for API calls
|
||||
bottle.get("/api/spots")(lambda: self.serve_api(self.get_spot_list_with_filters()))
|
||||
bottle.get("/api/alerts")(lambda: self.serve_api(self.get_alert_list_with_filters()))
|
||||
bottle.get("/api/options")(lambda: self.serve_api(self.get_options()))
|
||||
bottle.get("/api/status")(lambda: self.serve_api(self.status_data))
|
||||
bottle.post("/api/spot")(lambda: self.accept_spot())
|
||||
# Routes for templated pages
|
||||
bottle.get("/")(lambda: self.serve_template('webpage_spots'))
|
||||
bottle.get("/alerts")(lambda: self.serve_template('webpage_alerts'))
|
||||
bottle.get("/about")(lambda: self.serve_template('webpage_about'))
|
||||
bottle.get("/apidocs")(lambda: self.serve_template('webpage_apidocs'))
|
||||
# Default route to serve from "webassets"
|
||||
@@ -90,7 +91,7 @@ class WebServer:
|
||||
spot.source = "API"
|
||||
spot.icon = "desktop"
|
||||
spot.infer_missing()
|
||||
self.spots.add(spot.guid, spot, expire=MAX_SPOT_AGE)
|
||||
self.spots.add(spot.id, spot, expire=MAX_SPOT_AGE)
|
||||
|
||||
response.content_type = 'application/json'
|
||||
response.set_header('Cache-Control', 'no-store')
|
||||
@@ -114,7 +115,7 @@ class WebServer:
|
||||
# Utility method to apply filters to the overall spot list and return only a subset. Enables query parameters in
|
||||
# the main "spots" GET call.
|
||||
def get_spot_list_with_filters(self):
|
||||
# Get the query (and the right one, with Bottle magic. This is a MultiDict object
|
||||
# Get the query (and the right one, with Bottle magic. This is a MultiDict object)
|
||||
query = bottle.request.query
|
||||
|
||||
# Create a shallow copy of the spot list, ordered by spot time. We'll then filter it accordingly.
|
||||
@@ -124,9 +125,9 @@ class WebServer:
|
||||
# value or a comma-separated list.
|
||||
# We can provide a "limit" number as well. Spots are always returned newest-first; "limit" limits to only the
|
||||
# most recent X spots.
|
||||
spot_guids = list(self.spots.iterkeys())
|
||||
spot_ids = list(self.spots.iterkeys())
|
||||
spots = []
|
||||
for k in spot_guids:
|
||||
for k in spot_ids:
|
||||
spots.append(self.spots.get(k))
|
||||
spots = sorted(spots, key=lambda spot: spot.time, reverse=True)
|
||||
for k in query.keys():
|
||||
@@ -167,6 +168,43 @@ class WebServer:
|
||||
spots = spots[:int(query.get("limit"))]
|
||||
return spots
|
||||
|
||||
|
||||
# Utility method to apply filters to the overall alert list and return only a subset. Enables query parameters in
|
||||
# the main "alerts" GET call.
|
||||
def get_alert_list_with_filters(self):
|
||||
# Get the query (and the right one, with Bottle magic. This is a MultiDict object)
|
||||
query = bottle.request.query
|
||||
|
||||
# Create a shallow copy of the alert list, ordered by alert time. We'll then filter it accordingly.
|
||||
# We can filter by received time with "received_since", which take a UNIX timestamp in seconds UTC.
|
||||
# We can also filter by source, sig, and dx_continent. Each of these accepts a single
|
||||
# value or a comma-separated list.
|
||||
# We can provide a "limit" number as well. Alerts are always returned newest-first; "limit" limits to only the
|
||||
# most recent X alerts.
|
||||
alert_ids = list(self.spots.iterkeys())
|
||||
alerts = []
|
||||
for k in alert_ids:
|
||||
alerts.append(self.spots.get(k))
|
||||
alerts = sorted(alerts, key=lambda spot: spot.time, reverse=True)
|
||||
for k in query.keys():
|
||||
match k:
|
||||
case "received_since":
|
||||
since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC)
|
||||
alerts = [s for s in alerts if s.received_time > since]
|
||||
case "source":
|
||||
sources = query.get(k).split(",")
|
||||
alerts = [s for s in alerts if s.source in sources]
|
||||
case "sig":
|
||||
sigs = query.get(k).split(",")
|
||||
alerts = [s for s in alerts if s.sig in sigs]
|
||||
case "dx_continent":
|
||||
dxconts = query.get(k).split(",")
|
||||
alerts = [s for s in alerts if s.dx_continent in dxconts]
|
||||
# If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys.
|
||||
if "limit" in query.keys():
|
||||
alerts = alerts[:int(query.get("limit"))]
|
||||
return alerts
|
||||
|
||||
# Return all the "options" for various things that the server is aware of. This can be fetched with an API call.
|
||||
# The idea is that this will include most of the things that can be provided as queries to the main spots call,
|
||||
# and thus a client can use this data to configure its filter controls.
|
||||
@@ -175,7 +213,8 @@ class WebServer:
|
||||
"modes": ALL_MODES,
|
||||
"mode_types": MODE_TYPES,
|
||||
"sigs": SIGS,
|
||||
# Sources are filtered for only ones that are enabled in config, no point letting the user toggle things that aren't even available.
|
||||
"sources": list(map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["providers"]))),
|
||||
# Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle things that aren't even available.
|
||||
"spot_sources": list(map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["spot_providers"]))),
|
||||
"alert_sources": list(map(lambda p: p["name"], filter(lambda p: p["enabled"], self.status_data["alert_providers"]))),
|
||||
"continents": CONTINENTS,
|
||||
"max_spot_age": MAX_SPOT_AGE}
|
||||
|
||||
Reference in New Issue
Block a user