mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-02-04 09:14:30 +00:00
Fix SSE connections not respecting filters #3
This commit is contained in:
@@ -58,6 +58,10 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
self.web_server_metrics["status"] = "OK"
|
self.web_server_metrics["status"] = "OK"
|
||||||
api_requests_counter.inc()
|
api_requests_counter.inc()
|
||||||
|
|
||||||
|
# request.arguments contains lists for each param key because technically the client can supply multiple,
|
||||||
|
# reduce that to just the first entry, and convert bytes to string
|
||||||
|
self.query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
|
||||||
|
|
||||||
# Create a alert queue and add it to the web server's list. The web server will fill this when alerts arrive
|
# Create a alert queue and add it to the web server's list. The web server will fill this when alerts arrive
|
||||||
self.alert_queue = Queue(maxsize=100)
|
self.alert_queue = Queue(maxsize=100)
|
||||||
self.sse_alert_queues.append(self.alert_queue)
|
self.sse_alert_queues.append(self.alert_queue)
|
||||||
@@ -86,7 +90,10 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
if self.alert_queue:
|
if self.alert_queue:
|
||||||
while not self.alert_queue.empty():
|
while not self.alert_queue.empty():
|
||||||
alert = self.alert_queue.get()
|
alert = self.alert_queue.get()
|
||||||
self.write_message(msg=json.dumps(alert, default=serialize_everything))
|
# If the new alert matches our param filters, send it to the client. If not, ignore it.
|
||||||
|
if alert_allowed_by_query(alert, self.query_params):
|
||||||
|
self.write_message(msg=json.dumps(alert, default=serialize_everything))
|
||||||
|
|
||||||
if self.alert_queue not in self.sse_alert_queues:
|
if self.alert_queue not in self.sse_alert_queues:
|
||||||
logging.error("Web server cleared up a queue of an active connection!")
|
logging.error("Web server cleared up a queue of an active connection!")
|
||||||
self.close()
|
self.close()
|
||||||
@@ -155,4 +162,10 @@ def alert_allowed_by_query(alert, query):
|
|||||||
dx_call_includes = query.get(k).strip()
|
dx_call_includes = query.get(k).strip()
|
||||||
if not alert.dx_call or dx_call_includes.upper() not in alert.dx_call.upper():
|
if not alert.dx_call or dx_call_includes.upper() not in alert.dx_call.upper():
|
||||||
return False
|
return False
|
||||||
|
case "text_includes":
|
||||||
|
text_includes = query.get(k).strip()
|
||||||
|
if (not alert.dx_call or text_includes.upper() not in alert.dx_call.upper()) \
|
||||||
|
and (not alert.comment or text_includes.upper() not in alert.comment.upper()) \
|
||||||
|
and (not alert.freqs_modes or text_includes.upper() not in alert.freqs_modes.upper()):
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
self.web_server_metrics["status"] = "OK"
|
self.web_server_metrics["status"] = "OK"
|
||||||
api_requests_counter.inc()
|
api_requests_counter.inc()
|
||||||
|
|
||||||
|
# request.arguments contains lists for each param key because technically the client can supply multiple,
|
||||||
|
# reduce that to just the first entry, and convert bytes to string
|
||||||
|
self.query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
|
||||||
|
|
||||||
# Create a spot queue and add it to the web server's list. The web server will fill this when spots arrive
|
# Create a spot queue and add it to the web server's list. The web server will fill this when spots arrive
|
||||||
self.spot_queue = Queue(maxsize=1000)
|
self.spot_queue = Queue(maxsize=1000)
|
||||||
self.sse_spot_queues.append(self.spot_queue)
|
self.sse_spot_queues.append(self.spot_queue)
|
||||||
@@ -88,7 +92,10 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
if self.spot_queue:
|
if self.spot_queue:
|
||||||
while not self.spot_queue.empty():
|
while not self.spot_queue.empty():
|
||||||
spot = self.spot_queue.get()
|
spot = self.spot_queue.get()
|
||||||
self.write_message(msg=json.dumps(spot, default=serialize_everything))
|
# If the new spot matches our param filters, send it to the client. If not, ignore it.
|
||||||
|
if spot_allowed_by_query(spot, self.query_params):
|
||||||
|
self.write_message(msg=json.dumps(spot, default=serialize_everything))
|
||||||
|
|
||||||
if self.spot_queue not in self.sse_spot_queues:
|
if self.spot_queue not in self.sse_spot_queues:
|
||||||
logging.error("Web server cleared up a queue of an active connection!")
|
logging.error("Web server cleared up a queue of an active connection!")
|
||||||
self.close()
|
self.close()
|
||||||
@@ -206,6 +213,11 @@ def spot_allowed_by_query(spot, query):
|
|||||||
dx_call_includes = query.get(k).strip()
|
dx_call_includes = query.get(k).strip()
|
||||||
if not spot.dx_call or dx_call_includes.upper() not in spot.dx_call.upper():
|
if not spot.dx_call or dx_call_includes.upper() not in spot.dx_call.upper():
|
||||||
return False
|
return False
|
||||||
|
case "text_includes":
|
||||||
|
text_includes = query.get(k).strip()
|
||||||
|
if (not spot.dx_call or text_includes.upper() not in spot.dx_call.upper()) \
|
||||||
|
and (not spot.comment or text_includes.upper() not in spot.comment.upper()):
|
||||||
|
return False
|
||||||
case "allow_qrt":
|
case "allow_qrt":
|
||||||
# If false, spots that are flagged as QRT are not returned.
|
# If false, spots that are flagged as QRT are not returned.
|
||||||
prevent_qrt = query.get(k).upper() == "FALSE"
|
prevent_qrt = query.get(k).upper() == "FALSE"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<p class="d-inline-flex gap-1">
|
<p class="d-inline-flex gap-1">
|
||||||
<span style="position: relative;">
|
<span style="position: relative;">
|
||||||
<i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 0px; top: 2px; padding: 10px; pointer-events: none;"></i>
|
<i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 0px; top: 2px; padding: 10px; pointer-events: none;"></i>
|
||||||
<input id="filter-dx-call" type="search" class="form-control" oninput="filtersUpdated();" placeholder="Callsign">
|
<input id="search" type="search" class="form-control" oninput="filtersUpdated();" placeholder="Search">
|
||||||
</span>
|
</span>
|
||||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ paths:
|
|||||||
default: false
|
default: false
|
||||||
- name: dx_call_includes
|
- name: dx_call_includes
|
||||||
in: query
|
in: query
|
||||||
description: "Limit the alerts to only ones where the DX callsign includes the supplied string (case-insensitive). Generally a complete callsign, but you can supply a shorter string for partial matches."
|
description: "Limit the spots to only ones where the DX callsign includes the supplied string (case-insensitive). Generally a complete callsign, but you can supply a shorter string for partial matches."
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
@@ -125,6 +125,12 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: text_includes
|
||||||
|
in: query
|
||||||
|
description: "Limit the spots to only ones where some significant text (DX callsign or comment) includes the supplied string (case-insensitive)."
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
- name: needs_good_location
|
- name: needs_good_location
|
||||||
in: query
|
in: query
|
||||||
description: "Return only spots with a 'good' location. (See the spot `dx_location_good` parameter for details. Useful for map-based clients, to avoid spots with 'bad' locations e.g. loads of cluster spots ending up in the centre of the DXCC entitity.)"
|
description: "Return only spots with a 'good' location. (See the spot `dx_location_good` parameter for details. Useful for map-based clients, to avoid spots with 'bad' locations e.g. loads of cluster spots ending up in the centre of the DXCC entitity.)"
|
||||||
@@ -215,7 +221,7 @@ paths:
|
|||||||
$ref: "#/components/schemas/Continent"
|
$ref: "#/components/schemas/Continent"
|
||||||
- name: dx_call_includes
|
- name: dx_call_includes
|
||||||
in: query
|
in: query
|
||||||
description: "Limit the alerts to only ones where the DX callsign includes the supplied string (case-insensitive). Generally a complete callsign, but you can supply a shorter string for partial matches."
|
description: "Limit the spots to only ones where the DX callsign includes the supplied string (case-insensitive). Generally a complete callsign, but you can supply a shorter string for partial matches."
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
@@ -225,6 +231,12 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: text_includes
|
||||||
|
in: query
|
||||||
|
description: "Limit the spots to only ones where some significant text (DX callsign or comment) includes the supplied string (case-insensitive)."
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
- name: needs_good_location
|
- name: needs_good_location
|
||||||
in: query
|
in: query
|
||||||
description: "Return only spots with a 'good' location. (See the spot `dx_location_good` parameter for details. Useful for map-based clients, to avoid spots with 'bad' locations e.g. loads of cluster spots ending up in the centre of the DXCC entitity.)"
|
description: "Return only spots with a 'good' location. (See the spot `dx_location_good` parameter for details. Useful for map-based clients, to avoid spots with 'bad' locations e.g. loads of cluster spots ending up in the centre of the DXCC entitity.)"
|
||||||
@@ -304,6 +316,12 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: text_includes
|
||||||
|
in: query
|
||||||
|
description: "Limit the alerts to only ones where some significant text (DX callsign, freqs/modes, or comment) includes the supplied string (case-insensitive)."
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Success
|
description: Success
|
||||||
@@ -359,6 +377,12 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: text_includes
|
||||||
|
in: query
|
||||||
|
description: "Limit the alerts to only ones where some significant text (DX callsign, freqs/modes, or comment) includes the supplied string (case-insensitive)."
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Success
|
description: Success
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ div.container {
|
|||||||
|
|
||||||
/* SPOTS/ALERTS PAGES, SETTINGS/STATUS AREAS */
|
/* SPOTS/ALERTS PAGES, SETTINGS/STATUS AREAS */
|
||||||
|
|
||||||
input#filter-dx-call {
|
input#search {
|
||||||
max-width: 12em;
|
max-width: 12em;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
@@ -337,7 +337,7 @@ div.band-spot:hover span.band-spot-info {
|
|||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
/* Filter/search DX Call field should be smaller on mobile */
|
/* Filter/search DX Call field should be smaller on mobile */
|
||||||
input#filter-dx-call {
|
input#search {
|
||||||
max-width: 9em;
|
max-width: 9em;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ let rowCount = 0;
|
|||||||
|
|
||||||
// Load spots and populate the table.
|
// Load spots and populate the table.
|
||||||
function loadSpots() {
|
function loadSpots() {
|
||||||
|
// If we have an ongoing SSE connection, stop it so it doesn't interfere with our reload
|
||||||
|
if (evtSource != null) {
|
||||||
|
evtSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the new query
|
||||||
$.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) {
|
$.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) {
|
||||||
// Store last updated time
|
// Store last updated time
|
||||||
lastUpdateTime = moment.utc();
|
lastUpdateTime = moment.utc();
|
||||||
@@ -14,17 +20,14 @@ function loadSpots() {
|
|||||||
// Update table
|
// Update table
|
||||||
updateTable();
|
updateTable();
|
||||||
// Start SSE connection to fetch updates in the background
|
// Start SSE connection to fetch updates in the background
|
||||||
restartSSEConnection();
|
startSSEConnection();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start an SSE connection (closing an existing one if it exists). This will then be used to add to the table on the
|
// Start an SSE connection (closing an existing one if it exists). This will then be used to add to the table on the
|
||||||
// fly.
|
// fly.
|
||||||
function restartSSEConnection() {
|
function startSSEConnection() {
|
||||||
if (evtSource != null) {
|
evtSource = new EventSource('/api/v1/spots/stream' + buildQueryString());
|
||||||
evtSource.close();
|
|
||||||
}
|
|
||||||
evtSource = new EventSource('/api/v1/spots/stream');
|
|
||||||
|
|
||||||
evtSource.onmessage = function(event) {
|
evtSource.onmessage = function(event) {
|
||||||
// Store last updated time
|
// Store last updated time
|
||||||
@@ -74,8 +77,8 @@ function buildQueryString() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
str = str + "limit=" + $("#spots-to-fetch option:selected").val();
|
str = str + "limit=" + $("#spots-to-fetch option:selected").val();
|
||||||
if ($("#filter-dx-call").val() != "") {
|
if ($("#search").val() != "") {
|
||||||
str = str + "&dx_call_includes=" + encodeURIComponent($("#filter-dx-call").val());
|
str = str + "&text_includes=" + encodeURIComponent($("#search").val());
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user