Extract "add spot" into its own page

This commit is contained in:
Ian Renton
2025-11-01 08:52:46 +00:00
parent 0c79436399
commit 69821f817b
6 changed files with 141 additions and 138 deletions

View File

@@ -6,11 +6,10 @@ from threading import Thread
import bottle
import pytz
from bottle import run, request, response, template
from prometheus_client import CONTENT_TYPE_LATEST, generate_latest
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION
from core.prometheus_metrics_handler import page_requests_counter, registry, get_metrics, api_requests_counter
from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter
from data.spot import Spot
@@ -33,6 +32,7 @@ class WebServer:
# Base template data
bottle.BaseTemplate.defaults['software_version'] = SOFTWARE_VERSION
bottle.BaseTemplate.defaults['allow_spotting'] = ALLOW_SPOTTING
# Routes for API calls
bottle.get("/api/v1/spots")(lambda: self.serve_spots_api())
@@ -45,6 +45,7 @@ class WebServer:
bottle.get("/map")(lambda: self.serve_template('webpage_map'))
bottle.get("/bands")(lambda: self.serve_template('webpage_bands'))
bottle.get("/alerts")(lambda: self.serve_template('webpage_alerts'))
bottle.get("/add-spot")(lambda: self.serve_template('webpage_add_spot'))
bottle.get("/status")(lambda: self.serve_template('webpage_status'))
bottle.get("/about")(lambda: self.serve_template('webpage_about'))
bottle.get("/apidocs")(lambda: self.serve_template('webpage_apidocs'))

View File

@@ -0,0 +1,52 @@
% rebase('webpage_base.tpl')
<div class="alert alert-warning fade show mb-0 mt-4" role="alert">
Please note that spots added to Spothole are not currently sent "upstream" to DX clusters or xOTA spotting sites.
</div>
<div class="mt-3">
<div id="add-spot-area" class="card mb-3">
<div class="card-header text-white bg-primary">
<div class="row">
<div class="col-auto me-auto">
Add a Spot
</div>
</div>
</div>
<div class="card-body">
<form class="row g-2">
<div class="col-auto">
<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;">
</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;">
</div>
<div class="col-auto">
<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;">
</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-bad"></div>
</div>
</div>
</div>
<script src="/js/common.js"></script>
<script src="/js/add-spot.js"></script>
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>

View File

@@ -62,6 +62,9 @@
<li class="nav-item ms-4"><a href="/map" class="nav-link" id="nav-link-map"><i class="fa-solid fa-map"></i> Map</a></li>
<li class="nav-item ms-4"><a href="/bands" class="nav-link" id="nav-link-bands"><i class="fa-solid fa-ruler-vertical"></i> Bands</a></li>
<li class="nav-item ms-4"><a href="/alerts" class="nav-link" id="nav-link-alerts"><i class="fa-solid fa-bell"></i> Alerts</a></li>
% 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
<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>

View File

@@ -14,11 +14,10 @@
</div>
<div class="col-auto">
<p class="d-inline-flex gap-1">
<div class="hideonmobile" style="position: relative; display: inline-block; top: 2px;">
<i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 0px; padding: 10px; pointer-events: none;"></i>
<span style="position: relative;">
<i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 0px; top: 2px; padding: 10px; pointer-events: none;"></i>
<input id="filter-dx-call" type="text" class="form-control me-3" oninput="filtersUpdated();" placeholder="Search for call" style="max-width: 10em; padding-left: 2em;">
</div>
<button id="add-spot-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleAddSpotPanel();"><i class="fa-solid fa-comment"></i> Add Spot</button>
</span>
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
</p>
@@ -188,55 +187,6 @@
</div>
</div>
<div id="add-spot-area" class="appearing-panel card mb-3">
<div class="card-header text-white bg-primary">
<div class="row">
<div class="col-auto me-auto">
Add a Spot
</div>
<div class="col-auto d-inline-flex">
<button id="close-add-spot-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeAddSpotPanel();"></button>
</div>
</div>
</div>
<div class="card-body">
<form class="row g-2">
<div class="col-auto">
<label for="add-spot-dx-call" class="form-label">DX Call</label>
<input type="text" class="form-control" id="add-spot-dx-call" placeholder="N0CALL" style="max-width: 8em;">
</div>
<div class="col-auto">
<label for="add-spot-freq" class="form-label">Frequency (kHz)</label>
<input type="text" class="form-control" id="add-spot-freq" placeholder="14100" style="max-width: 8em;">
</div>
<div class="col-auto">
<label for="add-spot-mode" class="form-label">Mode</label>
<input type="text" class="form-control" id="add-spot-mode" placeholder="SSB" style="max-width: 6em;">
</div>
<div class="col-auto">
<label for="add-spot-comment" class="form-label">Comment</label>
<input type="text" class="form-control" id="add-spot-comment" placeholder="59 TNX QSO 73" style="max-width: 12em;">
</div>
<div class="col-auto">
<label for="add-spot-de-call" class="form-label">Your Call</label>
<input type="text" class="form-control" id="add-spot-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="post-spot-result-good"></span>
</div>
</form>
<div id="post-spot-result-bad"></div>
<div class="alert alert-warning alert-dismissible fade show mb-0 mt-4" role="alert">
Please note that spots added to Spothole are not currently sent "upstream" to DX clusters or xOTA spotting sites.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
</div>
<div id="table-container"></div>
</div>

80
webassets/js/add-spot.js Normal file
View File

@@ -0,0 +1,80 @@
// Method called to add a spot to the server
function addSpot() {
try {
// Save settings (this will save "your call" for future use)
saveSettings();
// Unpack the user's entered values
var dx = $("#dx-call").val().toUpperCase();
var freqStr = $("#freq").val();
var mode = $("#mode").val().toUpperCase();
var comment = $("#comment").val();
var de = $("#de-call").val().toUpperCase();
var spot = {}
if (dx != "") {
spot["dx_call"] = dx;
} else {
showAddSpotError("A DX callsign is required in order to spot.");
return;
}
if (freqStr != "") {
spot["freq"] = parseFloat(freqStr) * 1000;
} else {
showAddSpotError("A frequency is required in order to spot.");
return;
}
if (mode != "") {
spot["mode"] = mode;
}
if (comment != "") {
spot["comment"] = comment;
}
if (de != "") {
spot["de_call"] = de;
}
spot["time"] = moment.utc().valueOf() / 1000.0;
$.ajax("/api/v1/spot", {
data : JSON.stringify(spot),
contentType : 'application/json',
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>");
setTimeout(() => {
$("#result-good").hide();
window.location.replace("/");
}, 1000);
},
error: function (result) {
showAddSpotError(result.responseText);
}
});
} catch (error) {
showAddSpotError(error);
}
return false;
}
// Show an "add spot" error.
function showAddSpotError(text) {
$("#result-bad").html("<div class='alert alert-danger alert-dismissible fade show mb-0 mt-4' role='alert'><i class='fa-solid fa-triangle-exclamation'></i> " + text + "<button type='button' class='btn-close' data-bs-dismiss='alert' aria-label='Close'></button></div>");
}
// Force callsign and mode capitalisation
$("#dx-call").change(function () {
$(this).val($(this).val().trim().toUpperCase());
});
$("#de-call").change(function () {
$(this).val($(this).val().trim().toUpperCase());
});
$("#mode").change(function () {
$(this).val($(this).val().trim().toUpperCase());
});
// Startup
$(document).ready(function() {
// Load settings from settings storage
loadSettings();
});

View File

@@ -330,64 +330,6 @@ function userGridUpdated() {
saveSettings();
}
// Method called to add a spot to the server
function addSpot() {
try {
var dx = $("#add-spot-dx-call").val().toUpperCase();
var freqStr = $("#add-spot-freq").val();
var mode = $("#add-spot-mode").val().toUpperCase();
var comment = $("#add-spot-comment").val();
var de = $("#add-spot-de-call").val().toUpperCase();
var spot = {}
if (dx != "") {
spot["dx_call"] = dx;
} else {
showAddSpotError("A DX callsign is required in order to spot.");
return;
}
if (freqStr != "") {
spot["freq"] = parseFloat(freqStr) * 1000;
} else {
showAddSpotError("A frequency is required in order to spot.");
return;
}
if (mode != "") {
spot["mode"] = mode;
}
if (comment != "") {
spot["comment"] = comment;
}
if (de != "") {
spot["de_call"] = de;
}
spot["time"] = moment.utc().valueOf() / 1000.0;
$.ajax("/api/v1/spot", {
data : JSON.stringify(spot),
contentType : 'application/json',
type : 'POST',
timeout: 10000,
success: async function (result) {
$("#post-spot-result-good").html("<button type='button' class='btn btn-success' style='margin-top: 2em;'><i class='fa-solid fa-check'></i> OK</button>");
setTimeout(() => $("#post-spot-result-good").hide(), 2000);
loadSpots();
},
error: function (result) {
showAddSpotError(result.responseText);
}
});
} catch (error) {
showAddSpotError(error);
}
return false;
}
// Show an "add spot" error.
function showAddSpotError(text) {
$("#post-spot-result-bad").html("<div class='alert alert-danger alert-dismissible fade show mb-0 mt-4' role='alert'><i class='fa-solid fa-triangle-exclamation'></i> " + text + "<button type='button' class='btn-close' data-bs-dismiss='alert' aria-label='Close'></button></div>");
}
// React to toggling/closing panels
function toggleFiltersPanel() {
// If we are going to show the filters panel, hide the display and add spot panels
@@ -395,10 +337,6 @@ function toggleFiltersPanel() {
$("#display-area").hide();
$("#display-button").button("toggle");
}
if (!$("#filters-area").is(":visible") && $("#add-spot-area").is(":visible")) {
$("#add-spot-area").hide();
$("#add-spot-button").button("toggle");
}
$("#filters-area").toggle();
}
function closeFiltersPanel() {
@@ -412,10 +350,6 @@ function toggleDisplayPanel() {
$("#filters-area").hide();
$("#filters-button").button("toggle");
}
if (!$("#display-area").is(":visible") && $("#add-spot-area").is(":visible")) {
$("#add-spot-area").hide();
$("#add-spot-button").button("toggle");
}
$("#display-area").toggle();
}
function closeDisplayPanel() {
@@ -423,23 +357,6 @@ function closeDisplayPanel() {
$("#display-area").hide();
}
function toggleAddSpotPanel() {
// If we are going to show the add spot panel, hide the filters and display panels
if (!$("#add-spot-area").is(":visible") && $("#filters-area").is(":visible")) {
$("#filters-area").hide();
$("#filters-button").button("toggle");
}
if (!$("#add-spot-area").is(":visible") && $("#display-area").is(":visible")) {
$("#display-area").hide();
$("#display-button").button("toggle");
}
$("#add-spot-area").toggle();
}
function closeAddSpotPanel() {
$("#add-spot-button").button("toggle");
$("#add-spot-area").hide();
}
// Display the intro box, unless the user has already dismissed it once.
function displayIntroBox() {
if (localStorage.getItem("intro-box-dismissed") == null) {