Merge branch 'main' into 95-send-spots-to-xota

# Conflicts:
#	webassets/apidocs/openapi.yml
This commit is contained in:
Ian Renton
2026-06-21 11:12:48 +01:00
11 changed files with 52 additions and 21 deletions

View File

@@ -8,7 +8,7 @@ from tornado import httputil
from tornado.web import Application from tornado.web import Application
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, PROPAGATION_MODES
from core.prometheus_metrics_handler import api_requests_counter from core.prometheus_metrics_handler import api_requests_counter
from core.utils import serialize_everything from core.utils import serialize_everything
@@ -61,6 +61,7 @@ class APIOptionsHandler(tornado.web.RequestHandler):
"spot_sources": spot_sources, "spot_sources": spot_sources,
"alert_sources": alert_sources, "alert_sources": alert_sources,
"continents": CONTINENTS, "continents": CONTINENTS,
"propagation_modes": PROPAGATION_MODES.values(),
"max_spot_age": MAX_SPOT_AGE, "max_spot_age": MAX_SPOT_AGE,
"spot_allowed": ALLOW_SPOTTING, "spot_allowed": ALLOW_SPOTTING,
"spot_submit_providers": spot_submit_providers} "spot_submit_providers": spot_submit_providers}

View File

@@ -19,13 +19,15 @@ class GMA(HTTPSpotProvider):
REF_INFO_URL_ROOT = "https://www.cqgma.org/api/ref/?" REF_INFO_URL_ROOT = "https://www.cqgma.org/api/ref/?"
def __init__(self, provider_config): def __init__(self, provider_config):
# Ensure there is an API key in our config, and set up the query URL using it # Ensure there is an API key in our config, and set up the query URL using it. If no key is provided,
# disable this spot provider.
self.api_key = provider_config.get("api-key", "") self.api_key = provider_config.get("api-key", "")
if self.api_key != "": if self.api_key == "":
super().__init__(provider_config, self.SPOTS_URL + "?key=" + self.api_key, self.POLL_INTERVAL_SEC) provider_config["enabled"] = False
else:
logging.warning("GMA spot provider configured but no api key was provided, this API will not be queried.") logging.warning("GMA spot provider configured but no api key was provided, this API will not be queried.")
super().__init__(provider_config, self.SPOTS_URL + "?key=" + self.api_key, self.POLL_INTERVAL_SEC)
def _http_response_to_spots(self, http_response): def _http_response_to_spots(self, http_response):
new_spots = [] new_spots = []
# Iterate through source data # Iterate through source data

View File

@@ -76,7 +76,7 @@
</div> </div>
<script src="/js/add-spot.js?v=1782030630"></script> <script src="/js/add-spot.js?v=1782036437"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active"); $("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -75,7 +75,7 @@
</div> </div>
<script src="/js/alerts.js?v=1782030630"></script> <script src="/js/alerts.js?v=1782036437"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active"); $("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -77,8 +77,8 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/spotsbandsandmap.js?v=1782030630"></script> <script src="/js/spotsbandsandmap.js?v=1782036437"></script>
<script src="/js/bands.js?v=1782030630"></script> <script src="/js/bands.js?v=1782036437"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-bands").addClass("active"); $("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -1,6 +1,6 @@
{% extends "skeleton.html" %} {% extends "skeleton.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/css/style.css?v=1782030630" type="text/css"> <link rel="stylesheet" href="/css/style.css?v=1782036437" type="text/css">
<link href="/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet"> <link href="/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet"> <link href="/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/vendor/css/solid-6.7.2.min.css" rel="stylesheet"> <link href="/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -10,10 +10,10 @@
<script src="/vendor/js/bootstrap-5.3.8.bundle.min.js"></script> <script src="/vendor/js/bootstrap-5.3.8.bundle.min.js"></script>
<script src="/vendor/js/tinycolor2-1.6.0.min.js"></script> <script src="/vendor/js/tinycolor2-1.6.0.min.js"></script>
<script src="/js/utils.js?v=1782030630"></script> <script src="/js/utils.js?v=1782036437"></script>
<script src="/js/ui-ham.js?v=1782030630"></script> <script src="/js/ui-ham.js?v=1782036437"></script>
<script src="/js/geo.js?v=1782030630"></script> <script src="/js/geo.js?v=1782036437"></script>
<script src="/js/common.js?v=1782030630"></script> <script src="/js/common.js?v=1782036437"></script>
{% end %} {% end %}
{% block body %} {% block body %}
<div class="container"> <div class="container">

View File

@@ -284,7 +284,7 @@
</div> </div>
<script src="/vendor/js/chart-4.4.9.umd.min.js"></script> <script src="/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/js/conditions.js?v=1782030630"></script> <script src="/js/conditions.js?v=1782036437"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active"); $("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -95,8 +95,8 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/spotsbandsandmap.js?v=1782030630"></script> <script src="/js/spotsbandsandmap.js?v=1782036437"></script>
<script src="/js/map.js?v=1782030630"></script> <script src="/js/map.js?v=1782036437"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-map").addClass("active"); $("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -116,8 +116,8 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/spotsbandsandmap.js?v=1782030630"></script> <script src="/js/spotsbandsandmap.js?v=1782036437"></script>
<script src="/js/spots.js?v=1782030630"></script> <script src="/js/spots.js?v=1782036437"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-spots").addClass("active"); $("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -59,7 +59,7 @@
</div> </div>
</div> </div>
<script src="/js/status.js?v=1782030630"></script> <script src="/js/status.js?v=1782036437"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");

View File

@@ -14,7 +14,7 @@ info:
Spothole's source code is located at https://git.ianrenton.com/ian/spothole and the README there provides setup instructions if you would like to run your own copy. A demonstration server of Spothole is located at https://spothole.app. Spothole's source code is located at https://git.ianrenton.com/ian/spothole and the README there provides setup instructions if you would like to run your own copy. A demonstration server of Spothole is located at https://spothole.app.
## Changelog ## Changelog
### 2.0 ### 2.0
* POST `/spot` now supports upstream submission to the spotting services associated with various SIGs. * POST `/spot` now supports upstream submission to the spotting services associated with various SIGs.
@@ -23,6 +23,10 @@ info:
* GET `/options` now returns `spot_submit_providers`, a map of SIG names to the names of providers that support upstream spot submission for that SIG. (This allows clients to present the user with options of where a new spot can be sent to.) * GET `/options` now returns `spot_submit_providers`, a map of SIG names to the names of providers that support upstream spot submission for that SIG. (This allows clients to present the user with options of where a new spot can be sent to.)
* **Breaking change:** A user's QRZ.com and HamQTH credentials are now supplied as request headers (`X-QRZ-Username`, `X-QRZ-Password`, `X-QRZ-Session-Key`, `X-HamQTH-Username`, `X-HamQTH-Password`, `X-HamQTH-Session-ID`) rather than query parameters, to keep credentials out of server logs. * **Breaking change:** A user's QRZ.com and HamQTH credentials are now supplied as request headers (`X-QRZ-Username`, `X-QRZ-Password`, `X-QRZ-Session-Key`, `X-HamQTH-Username`, `X-HamQTH-Password`, `X-HamQTH-Session-ID`) rather than query parameters, to keep credentials out of server logs.
### 1.4
* Spots can now include a "propagation_mode" field, and the `/options` call enumerates the options that can have.
### 1.3 ### 1.3
* `/solar` response now includes `ionosonde_data`, which contains ionosonde station measurements (LUF, foF2 and MUF) sourced from the GIRO Data Center as well as implied band states. * `/solar` response now includes `ionosonde_data`, which contains ionosonde station measurements (LUF, foF2 and MUF) sourced from the GIRO Data Center as well as implied band states.
@@ -912,6 +916,20 @@ components:
- NONE - NONE
example: SPOT example: SPOT
PropagationMode:
type: string
enum:
- F2 layer ionospheric
- Sporadic-E
- Tropospheric ducting
- Trans-Equatorial Propagation
- Earth-Moon-Earth
- Aurora
- Meteor scatter
- Rain scatter
- Aircraft scatter
example: Sporadic-E
LocationSourceForSpot: LocationSourceForSpot:
type: string type: string
enum: enum:
@@ -1167,6 +1185,11 @@ components:
type: string type: string
description: The ID the source gave it, if any. description: The ID the source gave it, if any.
example: "GUID-123456" example: "GUID-123456"
propagation_mode:
description: >
Propagation mode, if known. This is only populated when the upstream spot specifically states it; Spothole
does not try to determine it using its own algorithm.
$ref: "#/components/schemas/PropagationMode"
SpotSubmission: SpotSubmission:
@@ -1881,6 +1904,11 @@ components:
items: items:
type: string type: string
example: "EU" example: "EU"
propagation_modes:
type: array
description: A list of all the supported propagation mode names.
items:
$ref: '#/components/schemas/PropagationMode'
max_spot_age: max_spot_age:
type: integer type: integer
description: > description: >