mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-23 21:25:12 +00:00
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.
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path)
|
||||
@@ -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,58 +55,51 @@ 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")})
|
||||
]
|
||||
@@ -117,6 +108,7 @@ class WebServer:
|
||||
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):
|
||||
|
||||
10
spothole.py
10
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"]:
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<p>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.</p>
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -69,8 +69,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/add-spot.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/add-spot.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -70,8 +70,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/alerts.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/alerts.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -76,9 +76,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1780997896"></script>
|
||||
<script src="/js/bands.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1780999608"></script>
|
||||
<script src="/js/bands.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "skeleton.html" %}
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/css/style.css?v=1780997896" type="text/css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=1780999608" type="text/css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
|
||||
@@ -19,9 +19,9 @@
|
||||
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1780997896"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1780997896"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1780997896"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1780999608"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1780999608"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1780999608"></script>
|
||||
{% end %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
@@ -42,9 +42,7 @@
|
||||
{% if allow_spotting %}
|
||||
<li class="nav-item ms-4"><a href="/add-spot" class="nav-link" id="nav-link-add-spot"><i class="fa-solid fa-comment"></i> Add Spot</a></li>
|
||||
{% end %}
|
||||
{% if has_hamqsl or has_noaa_forecast %}
|
||||
<li class="nav-item ms-4"><a href="/conditions" class="nav-link" id="nav-link-conditions"><i class="fa-solid fa-sun"></i> Conditions</a></li>
|
||||
{% end %}
|
||||
<li class="nav-item ms-4"><a href="/status" class="nav-link" id="nav-link-status"><i class="fa-solid fa-chart-simple"></i> Status</a></li>
|
||||
<li class="nav-item ms-4"><a href="/about" class="nav-link" id="nav-link-about"><i class="fa-solid fa-circle-info"></i> About</a></li>
|
||||
<li class="nav-item ms-4"><a href="/apidocs" class="nav-link" id="nav-link-api"><i class="fa-solid fa-gear"></i> API</a></li>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
{% if has_hamqsl %}
|
||||
<div id="hamqsl-section" style="display:none">
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">
|
||||
Propagation Conditions
|
||||
@@ -126,15 +126,16 @@
|
||||
</div>
|
||||
<div class="row border-bottom align-items-start me-0">
|
||||
<div class="col-12 col-md-2 fw-bold py-2">Electron Flux</div>
|
||||
<div id="sw-electron-vals" class="col-12 col-md-3 py-2"><strong id="sw-electron-flux"></strong> efu</div>
|
||||
<div id="sw-electron-vals" class="col-12 col-md-3 py-2"><strong id="sw-electron-flux"></strong> efu
|
||||
</div>
|
||||
<div id="sw-electron-desc" class="col-12 col-md-7 py-2"></div>
|
||||
</div>
|
||||
<div class="form-text mt-3">Data from <a href="https://hamqsl.com">HamQSL.com</a>.</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
{% if has_noaa_forecast %}
|
||||
<div id="noaa-section" style="display:none">
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">
|
||||
Solar Weather Forecast
|
||||
@@ -171,9 +172,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
{% if has_giro_ionosonde %}
|
||||
<div id="ionosonde-section" style="display:none">
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">
|
||||
Ionosonde Data
|
||||
@@ -186,33 +187,45 @@
|
||||
</select>
|
||||
</div>
|
||||
<div id="ionosonde-latest" class="mb-3">
|
||||
<div id="ionosonde-no-data" class="alert alert-warning mt-2 mb-0 py-2 js-hidden">No data available for this station.</div>
|
||||
<div id="ionosonde-no-data" class="alert alert-warning mt-2 mb-0 py-2 js-hidden">No data available for
|
||||
this station.
|
||||
</div>
|
||||
<div id="ionosonde-data-rows" class="js-hidden">
|
||||
<div class="row align-items-center me-0">
|
||||
<div class="col-12 py-2 text-muted">Latest values as of <span id="ionosonde-latest-time"></span></div>
|
||||
<div class="col-12 py-2 text-muted">Latest values as of <span id="ionosonde-latest-time"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row align-items-center me-0">
|
||||
<div class="col-12 col-md-4 py-2">LUF: <strong id="ionosonde-latest-luf"></strong></div>
|
||||
<div class="col-12 col-md-4 py-2">foF2: <strong id="ionosonde-latest-fof2"></strong></div>
|
||||
<div class="col-12 col-md-4 py-2">MUF (3000 km): <strong id="ionosonde-latest-muf"></strong></div>
|
||||
<div class="col-12 col-md-4 py-2">MUF (3000 km): <strong id="ionosonde-latest-muf"></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ionosonde-stale-warning" class="alert alert-warning mt-2 mb-0 py-2 js-hidden">Data is more
|
||||
than 12 hours old!
|
||||
</div>
|
||||
<div id="ionosonde-stale-warning" class="alert alert-warning mt-2 mb-0 py-2 js-hidden">Data is more than 12 hours old!</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ionosonde-band-state" class="mb-3 js-hidden">
|
||||
<table class="table table-sm table-bordered mb-0 d-none d-md-table table-fixed-on-desktop">
|
||||
<thead><tr id="ionosonde-band-state-head"></tr></thead>
|
||||
<tbody><tr id="ionosonde-band-state-row"></tr></tbody>
|
||||
<thead>
|
||||
<tr id="ionosonde-band-state-head"></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr id="ionosonde-band-state-row"></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-sm table-bordered mb-0 d-md-none">
|
||||
<tbody id="ionosonde-band-state-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<canvas id="ionosonde-chart" class="mt-3 mb-3 hideonmobile"></canvas>
|
||||
<div class="form-text mt-2">Data from the <a href="https://lgdc.uml.edu/">Lowell GIRO Data Center</a> and/or <a href="https://prop.kc2g.com/">KC2G</a>.</div>
|
||||
<div class="form-text mt-2">Data from the <a href="https://lgdc.uml.edu/">Lowell GIRO Data Center</a> and/or
|
||||
<a href="https://prop.kc2g.com/">KC2G</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">
|
||||
@@ -271,8 +284,8 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/conditions.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/conditions.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-conditions").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1780997896"></script>
|
||||
<script src="/js/map.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1780999608"></script>
|
||||
<script src="/js/map.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -104,9 +104,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1780997896"></script>
|
||||
<script src="/js/spots.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1780999608"></script>
|
||||
<script src="/js/spots.js?v=1780999608"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -59,8 +59,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1780997896"></script>
|
||||
<script src="/js/status.js?v=1780997896"></script>
|
||||
<script src="/js/common.js?v=1780999608"></script>
|
||||
<script src="/js/status.js?v=1780999608"></script>
|
||||
<script>
|
||||
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
|
||||
</script>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user