mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-03-16 21:04:30 +00:00
Compare commits
5 Commits
471c487132
...
fa92657d9c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa92657d9c | ||
|
|
30fc333c8b | ||
|
|
0570b39e09 | ||
|
|
1ed543872a | ||
|
|
812d031a2c |
@@ -38,7 +38,7 @@ class CleanupTimer:
|
||||
for id in list(self.alerts.iterkeys()):
|
||||
alert = self.alerts[id]
|
||||
if alert.expired():
|
||||
self.alerts.evict(id)
|
||||
self.alerts.delete(id)
|
||||
|
||||
self.status = "OK"
|
||||
self.last_cleanup_time = datetime.now(pytz.UTC)
|
||||
|
||||
@@ -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="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="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="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="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="WAI", description="Worked All Ireland", icon="table-cells-large", ref_regex=r"[A-Z][0-9]{2}")
|
||||
]
|
||||
|
||||
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
|
||||
|
||||
@@ -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.lookup_helper import lookup_helper
|
||||
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
|
||||
|
||||
|
||||
@@ -140,6 +142,14 @@ class WebServer:
|
||||
json_spot = json.loads(post_data)
|
||||
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
|
||||
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
|
||||
response.content_type = 'application/json'
|
||||
@@ -171,6 +181,13 @@ class WebServer:
|
||||
response.status = 422
|
||||
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.
|
||||
spot.source = "API"
|
||||
if not spot.sig:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div id="add-spot-intro-box" class="permanently-dismissible-box mt-3">
|
||||
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||
<i class="fa-solid fa-circle-info"></i> <strong>Adding spots to Spothole</strong><br/>This page is implemented as a proof of concept for adding spots to the Spothole system. Currently, spots added in this way are only visible within Spothole and are not sent "upstream" to DX clusters or xOTA spotting sites. The functionality might be extended to include this in future if there is demand for it. If you'd like this to be added, please give a thumbs-up on <a href="https://git.ianrenton.com/ian/spothole/issues/70" target="_new" class="alert-link">issue #70</a> or get in touch via email.
|
||||
<i class="fa-solid fa-circle-info"></i> <strong>Adding spots to Spothole</strong><br/>This page is implemented as a proof of concept for adding spots to the Spothole system. Currently, spots added in this way are only visible within Spothole and are not sent "upstream" to DX clusters or xOTA spotting sites. The functionality might be extended to include this in future if there is demand for it. If you'd like this to be added, please give a thumbs-up on <a href="https://git.ianrenton.com/ian/spothole/issues/39" target="_new" class="alert-link">issue #39</a> or get in touch via email.
|
||||
<button type="button" id="add-spot-intro-box-dismiss" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,38 +17,52 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="row g-2">
|
||||
<form class="row g-3">
|
||||
<div class="col-auto">
|
||||
<label for="dx-call" class="form-label">DX Call</label>
|
||||
<label for="dx-call" class="form-label">DX Call *</label>
|
||||
<input type="text" class="form-control" id="dx-call" placeholder="N0CALL" style="max-width: 8em;">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="freq" class="form-label">Frequency (kHz)</label>
|
||||
<input type="text" class="form-control" id="freq" placeholder="14100" style="max-width: 8em;">
|
||||
<label for="freq" class="form-label">Frequency (kHz) *</label>
|
||||
<input type="text" class="form-control" id="freq" placeholder="e.g. 14100" style="max-width: 8em;">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="mode" class="form-label">Mode</label>
|
||||
<input type="text" class="form-control" id="mode" placeholder="SSB" style="max-width: 6em;">
|
||||
<select id="mode" class="form-select">
|
||||
<option value="" selected></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="dx-grid" class="form-label">DX Grid (Optional)</label>
|
||||
<input type="text" class="form-control" id="dx-grid" placeholder="AA00aa" style="max-width: 8em;">
|
||||
<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="comment" class="form-label">Comment (Optional)</label>
|
||||
<input type="text" class="form-control" id="comment" placeholder="59 TNX QSO 73" style="max-width: 12em;">
|
||||
<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">
|
||||
<label for="de-call" class="form-label">Your Call</label>
|
||||
<label for="dx-grid" class="form-label">DX Grid</label>
|
||||
<input type="text" class="form-control" id="dx-grid" placeholder="e.g. AA00aa" style="max-width: 8em;">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="comment" class="form-label">Comment</label>
|
||||
<input type="text" class="form-control" id="comment" placeholder="e.g. 59 TNX QSO 73" style="max-width: 12em;">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="de-call" class="form-label">Your Call *</label>
|
||||
<input type="text" class="form-control storeable-text" id="de-call" placeholder="N0CALL" style="max-width: 8em;">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-primary" style="margin-top: 2em;" onclick="addSpot();">Spot</button>
|
||||
<span id="result-good"></span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="result-good"></div>
|
||||
<div id="result-bad"></div>
|
||||
|
||||
<p class="small mt-4 mb-1">* Required field</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% rebase('webpage_base.tpl')
|
||||
|
||||
<div id="map">
|
||||
<div class="mt-3 px-3" style="z-index: 1002; position: relative;">
|
||||
<div id="maptools" class="mt-3 px-3" style="z-index: 1002; position: relative;">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto pt-3"></div>
|
||||
<div class="col-auto">
|
||||
|
||||
@@ -246,13 +246,24 @@ div.band-spot:hover span.band-spot-info {
|
||||
/* GENERAL MOBILE SUPPORT */
|
||||
|
||||
@media (max-width: 991.99px) {
|
||||
/* General "hide this on mobile" class */
|
||||
.hideonmobile {
|
||||
display: none !important;
|
||||
}
|
||||
/* Make map stretch to horizontal screen edges */
|
||||
div#map, div#table-container, div#bands-container {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
/* Avoid map page filters panel being larger than the map itself */
|
||||
#maptools .appearing-panel {
|
||||
max-height: 30em;
|
||||
}
|
||||
#maptools .appearing-panel .card-body {
|
||||
max-height: 26em;
|
||||
overflow: scroll;
|
||||
}
|
||||
/* Filter/search DX Call field should be smaller on mobile */
|
||||
input#filter-dx-call {
|
||||
max-width: 6em;
|
||||
margin-right: 0;
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
// Load server options. Once a successful callback is made from this, we can populate the choice boxes in the form and load
|
||||
// any saved values from local storage.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v1/options', function(jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Populate modes drop-down
|
||||
$.each(options["modes"], function (i, m) {
|
||||
$('#mode').append($('<option>', {
|
||||
value: m,
|
||||
text : m
|
||||
}));
|
||||
});
|
||||
|
||||
// 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
|
||||
loadSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Method called to add a spot to the server
|
||||
function addSpot() {
|
||||
try {
|
||||
@@ -7,7 +35,9 @@ function addSpot() {
|
||||
// Unpack the user's entered values
|
||||
var dx = $("#dx-call").val().toUpperCase();
|
||||
var freqStr = $("#freq").val();
|
||||
var mode = $("#mode").val().toUpperCase();
|
||||
var mode = $("#mode")[0].value;
|
||||
var sig = $("#sig")[0].value;
|
||||
var sigRef = $("#sig-ref").val();
|
||||
var dxGrid = $("#dx-grid").val();
|
||||
var comment = $("#comment").val();
|
||||
var de = $("#de-call").val().toUpperCase();
|
||||
@@ -28,6 +58,12 @@ function addSpot() {
|
||||
if (mode != "") {
|
||||
spot["mode"] = mode;
|
||||
}
|
||||
if (sig != "") {
|
||||
spot["sig"] = sig;
|
||||
}
|
||||
if (sigRef != "") {
|
||||
spot["sig_refs"] = [{id: sigRef}];
|
||||
}
|
||||
if (dxGrid != "") {
|
||||
spot["dx_grid"] = dxGrid;
|
||||
}
|
||||
@@ -48,7 +84,7 @@ function addSpot() {
|
||||
type : 'POST',
|
||||
timeout: 10000,
|
||||
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("");
|
||||
setTimeout(() => {
|
||||
$("#result-good").hide();
|
||||
@@ -93,8 +129,8 @@ function displayIntroBox() {
|
||||
|
||||
// Startup
|
||||
$(document).ready(function() {
|
||||
// Load settings from settings storage
|
||||
loadSettings();
|
||||
// Load options
|
||||
loadOptions();
|
||||
// Display intro box
|
||||
displayIntroBox();
|
||||
});
|
||||
@@ -249,4 +249,6 @@ $(document).ready(function() {
|
||||
setUpMap();
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
// Prevent scrolling actions in the popup menus being passed through to the map
|
||||
L.DomEvent.disableScrollPropagation(document.getElementById('maptools'));
|
||||
});
|
||||
Reference in New Issue
Block a user