Add Spot page to allow sig and sig_ref entries. Closes #71

This commit is contained in:
Ian Renton
2025-11-01 12:38:57 +00:00
parent 1ed543872a
commit 0570b39e09
4 changed files with 52 additions and 9 deletions

View File

@@ -24,11 +24,11 @@ SIGS = [
SIG(name="SIOTA", description="Silos on the Air", icon="wheat-awn", ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d"), SIG(name="SIOTA", description="Silos on the Air", icon="wheat-awn", ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d"),
SIG(name="WCA", description="World Castles Award", icon="chess-rook", ref_regex=r"[A-Z0-9]{1,3}\-\d+"), SIG(name="WCA", description="World Castles Award", icon="chess-rook", ref_regex=r"[A-Z0-9]{1,3}\-\d+"),
SIG(name="ZLOTA", description="New Zealand on the Air", icon="kiwi-bird", ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d+"), SIG(name="ZLOTA", description="New Zealand on the Air", icon="kiwi-bird", ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d+"),
SIG(name="WOTA", description="Wainwrights on the Air", icon="w", ref_regex=r"[A-Z]{3}-[0-9]{2}"),
SIG(name="BOTA", description="Beaches on the Air", icon="water"),
SIG(name="KRMNPA", description="Keith Roget Memorial National Parks Award", icon="earth-oceania", ref_regex=r""), SIG(name="KRMNPA", description="Keith Roget Memorial National Parks Award", icon="earth-oceania", ref_regex=r""),
SIG(name="WAB", description="Worked All Britain", icon="table-cells-large", ref_regex=r"[A-Z]{1,2}[0-9]{2}"), SIG(name="WAB", description="Worked All Britain", icon="table-cells-large", ref_regex=r"[A-Z]{1,2}[0-9]{2}"),
SIG(name="WAI", description="Worked All Ireland", icon="table-cells-large", ref_regex=r"[A-Z][0-9]{2}"), SIG(name="WAI", description="Worked All Ireland", icon="table-cells-large", ref_regex=r"[A-Z][0-9]{2}")
SIG(name="WOTA", description="Wainwrights on the Air", icon="w", ref_regex=r"[A-Z]{3}-[0-9]{2}"),
SIG(name="BOTA", description="Beaches on the Air", icon="water")
] ]
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA". # Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".

View File

@@ -12,6 +12,8 @@ from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND
from core.lookup_helper import lookup_helper from core.lookup_helper import lookup_helper
from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter
from core.sig_utils import get_ref_regex_for_sig
from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
@@ -140,6 +142,14 @@ class WebServer:
json_spot = json.loads(post_data) json_spot = json.loads(post_data)
spot = Spot(**json_spot) spot = Spot(**json_spot)
# Converting to a spot object this way won't have coped with sig_ref objects, so fix that. (Would be nice to
# redo this in a functional style)
if spot.sig_refs:
real_sig_refs = []
for dict_obj in spot.sig_refs:
real_sig_refs.append(json.loads(json.dumps(dict_obj), object_hook=lambda d: SIGRef(**d)))
spot.sig_refs = real_sig_refs
# Reject if no timestamp, frequency, dx_call or de_call # Reject if no timestamp, frequency, dx_call or de_call
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call: if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
response.content_type = 'application/json' response.content_type = 'application/json'
@@ -171,6 +181,13 @@ class WebServer:
response.status = 422 response.status = 422
return json.dumps("Error - '" + spot.dx_grid + "' does not look like a valid Maidenhead grid.", default=serialize_everything) return json.dumps("Error - '" + spot.dx_grid + "' does not look like a valid Maidenhead grid.", default=serialize_everything)
# Reject if sig_ref format incorrect for sig
print(spot.sig_refs[0])
if spot.sig and spot.sig_refs and len(spot.sig_refs) > 0 and spot.sig_refs[0].id and get_ref_regex_for_sig(spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
response.content_type = 'application/json'
response.status = 422
return json.dumps("Error - '" + spot.sig_refs[0].id + "' does not look like a valid reference for " + spot.sig + ".", default=serialize_everything)
# infer missing data, and add it to our database. # infer missing data, and add it to our database.
spot.source = "API" spot.source = "API"
if not spot.sig: if not spot.sig:

View File

@@ -24,21 +24,31 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="freq" class="form-label">Frequency (kHz) *</label> <label for="freq" class="form-label">Frequency (kHz) *</label>
<input type="text" class="form-control" id="freq" placeholder="14100" style="max-width: 8em;"> <input type="text" class="form-control" id="freq" placeholder="e.g. 14100" style="max-width: 8em;">
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="mode" class="form-label">Mode</label> <label for="mode" class="form-label">Mode</label>
<select id="mode" class="storeable-select form-select" style="max-width: 6em;"> <select id="mode" class="form-select">
<option value="" selected></option> <option value="" selected></option>
</select> </select>
</div> </div>
<div class="col-auto">
<label for="sig" class="form-label">SIG</label>
<select id="sig" class="form-select">
<option value="" selected></option>
</select>
</div>
<div class="col-auto">
<label for="sig-ref" class="form-label">SIG Reference</label>
<input type="text" class="form-control" id="sig-ref" placeholder="e.g. GB-0001" style="max-width: 8em;">
</div>
<div class="col-auto"> <div class="col-auto">
<label for="dx-grid" class="form-label">DX Grid</label> <label for="dx-grid" class="form-label">DX Grid</label>
<input type="text" class="form-control" id="dx-grid" placeholder="AA00aa" style="max-width: 8em;"> <input type="text" class="form-control" id="dx-grid" placeholder="e.g. AA00aa" style="max-width: 8em;">
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="comment" class="form-label">Comment</label> <label for="comment" class="form-label">Comment</label>
<input type="text" class="form-control" id="comment" placeholder="59 TNX QSO 73" style="max-width: 12em;"> <input type="text" class="form-control" id="comment" placeholder="e.g. 59 TNX QSO 73" style="max-width: 12em;">
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="de-call" class="form-label">Your Call *</label> <label for="de-call" class="form-label">Your Call *</label>
@@ -46,10 +56,10 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button type="button" class="btn btn-primary" style="margin-top: 2em;" onclick="addSpot();">Spot</button> <button type="button" class="btn btn-primary" style="margin-top: 2em;" onclick="addSpot();">Spot</button>
<span id="result-good"></span>
</div> </div>
</form> </form>
<div id="result-good"></div>
<div id="result-bad"></div> <div id="result-bad"></div>
<p class="small mt-4 mb-1">* Required field</p> <p class="small mt-4 mb-1">* Required field</p>

View File

@@ -13,6 +13,14 @@ function loadOptions() {
})); }));
}); });
// Populate SIG drop-down
$.each(options["sigs"], function (i, sig) {
$('#sig').append($('<option>', {
value: sig.name,
text : sig.name
}));
});
// Load settings from settings storage now all the controls are available // Load settings from settings storage now all the controls are available
loadSettings(); loadSettings();
}); });
@@ -28,6 +36,8 @@ function addSpot() {
var dx = $("#dx-call").val().toUpperCase(); var dx = $("#dx-call").val().toUpperCase();
var freqStr = $("#freq").val(); var freqStr = $("#freq").val();
var mode = $("#mode")[0].value; var mode = $("#mode")[0].value;
var sig = $("#sig")[0].value;
var sigRef = $("#sig-ref").val();
var dxGrid = $("#dx-grid").val(); var dxGrid = $("#dx-grid").val();
var comment = $("#comment").val(); var comment = $("#comment").val();
var de = $("#de-call").val().toUpperCase(); var de = $("#de-call").val().toUpperCase();
@@ -48,6 +58,12 @@ function addSpot() {
if (mode != "") { if (mode != "") {
spot["mode"] = mode; spot["mode"] = mode;
} }
if (sig != "") {
spot["sig"] = sig;
}
if (sigRef != "") {
spot["sig_refs"] = [{id: sigRef}];
}
if (dxGrid != "") { if (dxGrid != "") {
spot["dx_grid"] = dxGrid; spot["dx_grid"] = dxGrid;
} }
@@ -68,7 +84,7 @@ function addSpot() {
type : 'POST', type : 'POST',
timeout: 10000, timeout: 10000,
success: async function (result) { success: async function (result) {
$("#result-good").html("<button type='button' class='btn btn-success' style='margin-top: 2em;'><i class='fa-solid fa-check'></i> OK</button>"); $("#result-good").html("<div class='alert alert-success fade show mb-0 mt-4' role='alert'><i class='fa-solid fa-check'></i> Spot submitted. Returning you to the spots list...</div>");
$("#result-bad").html(""); $("#result-bad").html("");
setTimeout(() => { setTimeout(() => {
$("#result-good").hide(); $("#result-good").hide();