From b725c34f7ce375e1b1603126e35da4a9c33d6470 Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Tue, 9 Jun 2026 11:06:48 +0100 Subject: [PATCH] General refactor to remove stuff being passed around the Tornado handlers when it doesn't need to be. Conditions provider presence is how handled on the JS side without needing special booleans sent to every page handler. --- core/constants.py | 5 +- server/handlers/pagetemplate.py | 9 +- server/webserver.py | 74 +++--- spothole.py | 10 +- templates/about.html | 2 +- templates/add_spot.html | 4 +- templates/alerts.html | 4 +- templates/bands.html | 6 +- templates/base.html | 10 +- templates/conditions.html | 417 ++++++++++++++++---------------- templates/map.html | 6 +- templates/spots.html | 6 +- templates/status.html | 4 +- webassets/js/conditions.js | 5 + 14 files changed, 281 insertions(+), 281 deletions(-) diff --git a/core/constants.py b/core/constants.py index 3d6a47c..ae8a00a 100644 --- a/core/constants.py +++ b/core/constants.py @@ -3,12 +3,11 @@ from data.band import Band from data.sig import SIG # General software -SOFTWARE_NAME = "Spothole by M0TRT" SOFTWARE_VERSION = "1.4-pre" # HTTP headers used for spot providers that use HTTP -HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + ", v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"} -HAMQTH_PRG = (SOFTWARE_NAME + " v" + SOFTWARE_VERSION + " operated by " + SERVER_OWNER_CALLSIGN).replace(" ", "_") +HTTP_HEADERS = {"User-Agent": "Spothole v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"} +HAMQTH_PRG = ("Spothole v" + SOFTWARE_VERSION + " operated by " + SERVER_OWNER_CALLSIGN).replace(" ", "_") # Special Interest Groups SIGS = [ diff --git a/server/handlers/pagetemplate.py b/server/handlers/pagetemplate.py index 69150b0..30898d9 100644 --- a/server/handlers/pagetemplate.py +++ b/server/handlers/pagetemplate.py @@ -11,12 +11,9 @@ from core.prometheus_metrics_handler import page_requests_counter class PageTemplateHandler(tornado.web.RequestHandler): """Handler for all HTML pages generated from templates""" - def initialize(self, template_name, web_server_metrics, has_hamqsl=False, has_noaa_forecast=False, has_giro_ionosonde=False): + def initialize(self, template_name, web_server_metrics): self._template_name = template_name self._web_server_metrics = web_server_metrics - self._has_hamqsl = has_hamqsl - self._has_noaa_forecast = has_noaa_forecast - self._has_giro_ionosonde = has_giro_ionosonde def get(self): # Metrics @@ -28,6 +25,4 @@ class PageTemplateHandler(tornado.web.RequestHandler): # Load named template, and provide variables used in templates self.render(self._template_name + ".html", software_version=SOFTWARE_VERSION, server_owner_callsign=SERVER_OWNER_CALLSIGN, allow_spotting=ALLOW_SPOTTING, - web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path, - has_hamqsl=self._has_hamqsl, has_noaa_forecast=self._has_noaa_forecast, - has_giro_ionosonde=self._has_giro_ionosonde) \ No newline at end of file + web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path) \ No newline at end of file diff --git a/server/webserver.py b/server/webserver.py index 50faa1c..d204fb6 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -5,12 +5,11 @@ import os import tornado from tornado.web import StaticFileHandler -from core.config import SERVER_OWNER_CALLSIGN, ALLOW_SPOTTING -from core.constants import SOFTWARE_VERSION +from core.config import ALLOW_SPOTTING, WEB_SERVER_PORT, API_ONLY_MODE from core.utils import empty_queue from server.handlers.api.addspot import APISpotHandler -from server.handlers.api.dxstats import APIDxStatsHandler from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler +from server.handlers.api.dxstats import APIDxStatsHandler from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler from server.handlers.api.options import APIOptionsHandler from server.handlers.api.solar_conditions import APISolarConditionsHandler @@ -23,7 +22,7 @@ from server.handlers.pagetemplate import PageTemplateHandler class WebServer: """Provides the public-facing web server.""" - def __init__(self, spots, alerts, solar_conditions, status_data, solar_condition_providers, port, api_only_mode=False): + def __init__(self, spots, alerts, solar_conditions, status_data): """Constructor""" self._spots = spots @@ -32,9 +31,8 @@ class WebServer: self._sse_spot_queues = [] self._sse_alert_queues = [] self._status_data = status_data - self._solar_condition_providers = solar_condition_providers - self._port = port - self._api_only_mode = api_only_mode + self._port = WEB_SERVER_PORT + self._api_only_mode = API_ONLY_MODE self._shutdown_event = asyncio.Event() self.web_server_metrics = { "last_page_access_time": None, @@ -57,66 +55,60 @@ class WebServer: async def _start_inner(self): """Start method (async). Sets up the Tornado application.""" - provider_classes = [type(p).__name__ for p in self._solar_condition_providers if p.enabled] - has_hamqsl = "HamQSL" in provider_classes - has_noaa_forecast = "NOAA3dayForecast" in provider_classes - has_giro_ionosonde = "GIROIonosonde" in provider_classes or "KC2GProp" in provider_classes - page_opts = {"web_server_metrics": self.web_server_metrics, "has_hamqsl": has_hamqsl, - "has_noaa_forecast": has_noaa_forecast, "has_giro_ionosonde": has_giro_ionosonde} + # Prepare a list of common arguments that are passed in to every API & page handler. This is just a basic thing + # to avoid copy-pasting the same thing to every route declaration below. + handler_opts = {"web_server_metrics": self.web_server_metrics} # API endpoints are always enabled api_routes = [ - (r"/api/v1/spots", APISpotsHandler, {"spots": self._spots, "web_server_metrics": self.web_server_metrics}), - (r"/api/v1/alerts", APIAlertsHandler, - {"alerts": self._alerts, "web_server_metrics": self.web_server_metrics}), + (r"/api/v1/spots", APISpotsHandler, {"spots": self._spots, **handler_opts}), + (r"/api/v1/alerts", APIAlertsHandler, {"alerts": self._alerts, **handler_opts}), (r"/api/v1/spots/stream", APISpotsStreamHandler, - {"sse_spot_queues": self._sse_spot_queues, "web_server_metrics": self.web_server_metrics}), + {"sse_spot_queues": self._sse_spot_queues, **handler_opts}), (r"/api/v1/alerts/stream", APIAlertsStreamHandler, - {"sse_alert_queues": self._sse_alert_queues, "web_server_metrics": self.web_server_metrics}), - (r"/api/v1/solar", APISolarConditionsHandler, - {"solar_conditions": self._solar_conditions, "web_server_metrics": self.web_server_metrics}), - (r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, "web_server_metrics": self.web_server_metrics}), - (r"/api/v1/options", APIOptionsHandler, - {"status_data": self._status_data, "web_server_metrics": self.web_server_metrics}), - (r"/api/v1/status", APIStatusHandler, - {"status_data": self._status_data, "web_server_metrics": self.web_server_metrics}), - (r"/api/v1/lookup/call", APILookupCallHandler, {"web_server_metrics": self.web_server_metrics}), - (r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {"web_server_metrics": self.web_server_metrics}), - (r"/api/v1/lookup/grid", APILookupGridHandler, {"web_server_metrics": self.web_server_metrics}), - (r"/api/v1/spot", APISpotHandler, {"spots": self._spots, "web_server_metrics": self.web_server_metrics}), + {"sse_alert_queues": self._sse_alert_queues, **handler_opts}), + (r"/api/v1/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}), + (r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}), + (r"/api/v1/options", APIOptionsHandler, {"status_data": self._status_data, **handler_opts}), + (r"/api/v1/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}), + (r"/api/v1/lookup/call", APILookupCallHandler, {**handler_opts}), + (r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}), + (r"/api/v1/lookup/grid", APILookupGridHandler, {**handler_opts}), + (r"/api/v1/spot", APISpotHandler, {"spots": self._spots, **handler_opts}), ] # If in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes if self._api_only_mode: logging.info("API-only mode is enabled. Web UI will not be served.") ui_routes = [ - (r"/", PageTemplateHandler, {"template_name": "api_only_home", **page_opts}) + (r"/", PageTemplateHandler, {"template_name": "api_only_home", **handler_opts}) ] else: ui_routes = [ - (r"/", PageTemplateHandler, {"template_name": "spots", **page_opts}), - (r"/map", PageTemplateHandler, {"template_name": "map", **page_opts}), - (r"/bands", PageTemplateHandler, {"template_name": "bands", **page_opts}), - (r"/alerts", PageTemplateHandler, {"template_name": "alerts", **page_opts}), - (r"/conditions", PageTemplateHandler, {"template_name": "conditions", **page_opts}), - (r"/status", PageTemplateHandler, {"template_name": "status", **page_opts}), - (r"/about", PageTemplateHandler, {"template_name": "about", **page_opts}) + (r"/", PageTemplateHandler, {"template_name": "spots", **handler_opts}), + (r"/map", PageTemplateHandler, {"template_name": "map", **handler_opts}), + (r"/bands", PageTemplateHandler, {"template_name": "bands", **handler_opts}), + (r"/alerts", PageTemplateHandler, {"template_name": "alerts", **handler_opts}), + (r"/conditions", PageTemplateHandler, {"template_name": "conditions", **handler_opts}), + (r"/status", PageTemplateHandler, {"template_name": "status", **handler_opts}), + (r"/about", PageTemplateHandler, {"template_name": "about", **handler_opts}) ] # Only allow the Add Spot page if spotting is allowed if ALLOW_SPOTTING: - ui_routes += [(r"/add-spot", PageTemplateHandler, {"template_name": "add_spot", **page_opts})] + ui_routes += [(r"/add-spot", PageTemplateHandler, {"template_name": "add_spot", **handler_opts})] # API docs, Prometheus metrics, and finally static assets are always available regardless of API-only mode. misc_routes = [ - (r"/apidocs", PageTemplateHandler, {"template_name": "apidocs", **page_opts}), + (r"/apidocs", PageTemplateHandler, {"template_name": "apidocs", **handler_opts}), (r"/metrics", PrometheusMetricsHandler), (r"/(.*)", StaticFileHandler, {"path": os.path.join(os.path.dirname(__file__), "../webassets")}) ] app = tornado.web.Application(api_routes + ui_routes + misc_routes, - template_path=os.path.join(os.path.dirname(__file__), "../templates"), - debug=False) + template_path=os.path.join(os.path.dirname(__file__), "../templates"), + debug=False) app.listen(self._port) + logging.info("Web server running on port " + str(WEB_SERVER_PORT)) await self._shutdown_event.wait() def notify_new_spot(self, spot): diff --git a/spothole.py b/spothole.py index e839659..f5455be 100644 --- a/spothole.py +++ b/spothole.py @@ -9,8 +9,8 @@ from diskcache import Cache from core.cleanup import CleanupTimer from data.solar_conditions import SolarConditions -from core.config import config, WEB_SERVER_PORT, SERVER_OWNER_CALLSIGN, API_ONLY_MODE -from core.constants import SOFTWARE_NAME, SOFTWARE_VERSION +from core.config import config, SERVER_OWNER_CALLSIGN +from core.constants import SOFTWARE_VERSION from core.lookup_helper import lookup_helper from core.status_reporter import StatusReporter from server.webserver import WebServer @@ -90,7 +90,7 @@ if __name__ == '__main__': logging.info("Starting...") logging.info( - "This is " + SOFTWARE_NAME + " version " + SOFTWARE_VERSION + ". This instance is run by " + SERVER_OWNER_CALLSIGN + ".") + "This is Spothole version " + SOFTWARE_VERSION + ". This instance is run by " + SERVER_OWNER_CALLSIGN + ".") # Shut down gracefully on SIGINT signal.signal(signal.SIGINT, shutdown) @@ -99,9 +99,7 @@ if __name__ == '__main__': lookup_helper.start() # Set up web server - web_server = WebServer(spots=spots, alerts=alerts, solar_conditions=solar_conditions, status_data=status_data, - solar_condition_providers=solar_condition_providers, port=WEB_SERVER_PORT, - api_only_mode=API_ONLY_MODE) + web_server = WebServer(spots=spots, alerts=alerts, solar_conditions=solar_conditions, status_data=status_data) # Fetch, set up and start spot providers for entry in config["spot-providers"]: diff --git a/templates/about.html b/templates/about.html index bfe8993..6f60b8f 100644 --- a/templates/about.html +++ b/templates/about.html @@ -69,7 +69,7 @@

This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.

- + {% end %} \ No newline at end of file diff --git a/templates/add_spot.html b/templates/add_spot.html index 9d31a2a..f3752b5 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -69,8 +69,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/alerts.html b/templates/alerts.html index 0634f06..7175443 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -70,8 +70,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/bands.html b/templates/bands.html index 80dea96..ae5986d 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -76,9 +76,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 1537f70..da1f35c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,6 @@ {% extends "skeleton.html" %} {% block head_extra %} - + @@ -19,9 +19,9 @@ integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn" crossorigin="anonymous"> - - - + + + {% end %} {% block body %}
@@ -42,9 +42,7 @@ {% if allow_spotting %} {% end %} - {% if has_hamqsl or has_noaa_forecast %} - {% end %} diff --git a/templates/conditions.html b/templates/conditions.html index b924c46..891b7c5 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -1,218 +1,231 @@ {% extends "base.html" %} {% block content %} -{% if has_hamqsl %} -
-
- Propagation Conditions + +
+ + - -
-
- Solar Weather -
-
-
-
Solar Flux
-
- SFI: - Sunspots: + +
Data from the Lowell GIRO Data Center and/or + KC2G.
-
-
-
-
Geomagnetic
-
- K: - A: - G - Noise: -
-
- . -
-
-
-
X-ray Flux
-
- - R
-
-
-
-
Proton Flux
-
- pfu - S -
-
-
-
-
Electron Flux
-
efu
-
-
-
Data from HamQSL.com.
-
-
-{% end %} - -{% if has_noaa_forecast %} -
-
- Solar Weather Forecast -
-
-
-
-
K-index Forecast
- -
-
-
-
-
Solar Storm Forecast
- - - - - -
-
-
-
Radio Blackout Forecast
- - - - - -
-
-
-
-{% end %} - -{% if has_giro_ionosonde %} -
-
- Ionosonde Data -
-
-
- - -
-
-
No data available for this station.
-
-
-
Latest values as of
-
-
-
LUF:
-
foF2:
-
MUF (3000 km):
-
-
Data is more than 12 hours old!
-
-
-
- - - -
- - -
-
- -
Data from the Lowell GIRO Data Center and/or KC2G.
-
-
-{% end %}
@@ -271,8 +284,8 @@
- - + + diff --git a/templates/map.html b/templates/map.html index d9a9353..7fc8628 100644 --- a/templates/map.html +++ b/templates/map.html @@ -94,9 +94,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/spots.html b/templates/spots.html index 4f3aedf..04e65c8 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -104,9 +104,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/status.html b/templates/status.html index 811415b..eb14006 100644 --- a/templates/status.html +++ b/templates/status.html @@ -59,8 +59,8 @@
- - + + diff --git a/webassets/js/conditions.js b/webassets/js/conditions.js index 194697e..c81ea54 100644 --- a/webassets/js/conditions.js +++ b/webassets/js/conditions.js @@ -17,6 +17,7 @@ function loadSolarConditions() { const hfConditionClass = {'Good': 'bg-success-subtle', 'Fair': 'bg-warning-subtle', 'Poor': 'bg-danger-subtle'}; if (jsonData.hf_conditions) { + $('#hamqsl-section').show(); Object.entries(jsonData.hf_conditions).forEach(function ([key, condition]) { const cell = $('#hf-conditions-' + key); cell.text(condition); @@ -116,6 +117,7 @@ function loadSolarConditions() { // Ionosonde if (jsonData.ionosonde_data && Object.keys(jsonData.ionosonde_data).length > 0) { + $('#ionosonde-section').show(); ionosondeData = jsonData.ionosonde_data; populateIonosondeDropdown(ionosondeData); renderIonosondeData(); @@ -123,6 +125,9 @@ function loadSolarConditions() { // Forecast + if (jsonData.k_index_forecast) { + $('#noaa-section').show(); + } renderKIndexForecast(jsonData.k_index_forecast); renderSolarStormForecast(jsonData.solar_storm_forecast); renderBlackoutForecast(jsonData.blackout_forecast_r1r2, jsonData.blackout_forecast_r3_or_greater);