8 Commits
30 changed files with 267 additions and 146 deletions
+33 -48
View File
@@ -318,93 +318,78 @@ To set up nginx as a reverse proxy that sits in front of Spothole, first ensure
Create a file at `/etc/nginx/sites-available/` called `spothole`. Give it the following contents, replacing
`spothole.app` with the domain name on which you want to run Spothole. If you changed the port on which Spothole runs,
update that on the "proxy_pass" line too.
update that on the "proxy_pass" line, and if you installed Spothole somewhere other than `/home/spothole/spothole`,
adjust the alias location for serving static files.
(The latter section, configuring the nginx server to serve static files directly, improves efficiency because it saves
Spothole itself from serving JS, CSS etc. files. If you can't do this for some reason, e.g. your nginx and spothole are
on different computers, you can omit the `location /static/ {}` block.)
```nginx
server {
server_name spothole.app;
# Global proxy settings
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_buffering on;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
# Wellknown area for Lets Encrypt
location /.well-known/ {
alias /var/www/html/.well-known/;
}
# Load static assets directly from the Spothole webassets directory
location /static/ {
alias /home/spothole/spothole/webassets/;
expires 1h;
add_header Cache-Control "public, max-age=3600, must-revalidate";
}
# SSE endpoints
location ~ ^/api/v1/(spots|alerts)/stream {
location ~ ^/api/v1/(spots|alerts)/stream/? {
proxy_pass http://127.0.0.1:8080;
# Allow keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set correct content type for SSE API calls
add_header Content-Type text/event-stream always;
# Set remove buffering, remove caching, add suitable timeouts for SSE API calls
# Remove buffering, remove caching, add suitable timeouts for SSE API calls
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
proxy_connect_timeout 10s;
proxy_send_timeout 24h;
proxy_set_header X-Accel-Buffering no;
add_header Cache-Control no-store always;
# Allow cross-origin requests to API
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin * always;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Access-Control-Allow-Origin * always;
}
# Other API endpoints
location /api/ {
proxy_pass http://127.0.0.1:8080;
# Allow keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set up buffering, remove caching, add suitable timeouts for API calls
proxy_buffering on;
# Remove buffering, remove caching, add suitable timeouts for API calls
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
add_header Cache-Control no-store always;
# Allow cross-origin requests to API
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin * always;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Static assets
# Templated pages
location / {
proxy_pass http://127.0.0.1:8080;
# Allow keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set up buffering and caching, add suitable timeouts for static asset requests
proxy_buffering on;
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
add_header Cache-Control "public, max-age=3600, must-revalidate" always;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Cache-Control "no-cache, must-revalidate" always;
}
}
```
@@ -467,7 +452,7 @@ To navigate your way around the source code, this list may help.
*HTML/JS/CSS front-end code*
* `/webassets` - Root for static files served by the web server
* `/webassets` - Root for static files served by the web server. These are all served from a path starting `/static/`.
* `/webassets/apidocs` - Contains the OpenAPI spec (`openapi.yml`)
* `/webassets/css` - CSS files used by the web front-end
* `/webassets/img` - image files used by the web front-end
+1
View File
@@ -4,6 +4,7 @@ from threading import Thread, Event
import pytz
import requests
from requests.exceptions import ConnectionError
from alertproviders.alert_provider import AlertProvider
from core.constants import HTTP_HEADERS
+13 -6
View File
@@ -11,6 +11,7 @@ from pyhamtools import LookupLib, Callinfo, callinfo
from pyhamtools.exceptions import APIKeyMissingError
from pyhamtools.frequency import freq_to_band
from pyhamtools.locator import latlong_to_locator
from requests.exceptions import ConnectionError
from requests_cache import CachedSession
from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE
@@ -501,9 +502,12 @@ class LookupHelper:
# Try the call as given, then fall back to the base call (strips /P, /M etc.)
calls_to_try = [call]
home_call = callinfo.Callinfo.get_homecall(call)
if home_call != call:
calls_to_try.append(home_call)
try:
home_call = callinfo.Callinfo.get_homecall(call)
if home_call != call:
calls_to_try.append(home_call)
except ValueError:
logging.debug("Could not look up home call for callsign %s", call)
for lookup_call in calls_to_try:
try:
@@ -573,9 +577,12 @@ class LookupHelper:
# Try the call as given, then fall back to the base call (strips /P, /M etc.)
calls_to_try = [call]
home_call = callinfo.Callinfo.get_homecall(call)
if home_call != call:
calls_to_try.append(home_call)
try:
home_call = callinfo.Callinfo.get_homecall(call)
if home_call != call:
calls_to_try.append(home_call)
except ValueError:
logging.debug("Could not look up home call for callsign %s", call)
for lookup_call in calls_to_try:
try:
+12 -2
View File
@@ -2,6 +2,7 @@ import csv
import logging
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from requests.exceptions import ConnectionError
from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE
from core.constants import SIGS, HTTP_HEADERS
@@ -103,8 +104,17 @@ def populate_sig_ref_info(sig_ref):
sig_ref.name = data["name"] if "name" in data else None
sig_ref.url = "https://www.cqgma.org/zinfo.php?ref=" + ref_id
sig_ref.grid = data["locator"] if "locator" in data else None
sig_ref.latitude = data["latitude"] if "latitude" in data else None
sig_ref.longitude = data["longitude"] if "longitude" in data else None
# For some things (just IOTA?) the GMA actually returns a box where "latitude" and "longitude" are
# the zeroest corner of the box, then "lat2" and "lng2" provide the other corner. We detect this
# and provide a single lat/lon for the centre. Otherwise if we don't have these extra parameters,
# just use the single point we have.
if "latitude" in data and "longitude" in data and "lat2" in data and "lng2" in data:
sig_ref.latitude = (float(data["latitude"]) + float(data["lat2"])) / 2.0
sig_ref.longitude = (float(data["longitude"]) + float(data["lng2"])) / 2.0
else:
sig_ref.latitude = float(data["latitude"]) if "latitude" in data else None
sig_ref.longitude = float(data["longitude"]) if "longitude" in data else None
elif not response.from_cache:
logging.warning("Malformed response looking up %s ref %s via GMA", sig, ref_id)
elif not response.from_cache:
+11
View File
@@ -0,0 +1,11 @@
import tornado.web
from core.config import BASE_URL
class ManifestHandler(tornado.web.RequestHandler):
"""Handler for manifest.webmanifest, which needs BASE_URL inserted and a custom content-type"""
def get(self):
self.set_header("Content-Type", "application/manifest+json; charset=UTF-8")
self.render("manifest.webmanifest", baseurl=BASE_URL)
+16
View File
@@ -0,0 +1,16 @@
import logging
from tornado.web import StaticFileHandler, HTTPError
class QuietStaticFileHandler(StaticFileHandler):
"""Minor override of logging in StaticFileHandler to log HTTP errors at debug level instead of their usual
warning level. Without this, attacks on Spothole which try to do path traversal attacks would log exceptions
from inside Tornado, and the server logs would contain a lot of this type of content. This effectively changes
the log level of these exceptions to DEBUG so they are only logged if DEBUG level logging is enabled."""
def log_exception(self, typ, value, tb):
if isinstance(value, HTTPError):
logging.debug(value)
return
super().log_exception(typ, value, tb)
+5 -2
View File
@@ -15,8 +15,10 @@ from server.handlers.api.options import APIOptionsHandler
from server.handlers.api.solar_conditions import APISolarConditionsHandler
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
from server.handlers.api.status import APIStatusHandler
from server.handlers.manifesthandler import ManifestHandler
from server.handlers.metrics import PrometheusMetricsHandler
from server.handlers.pagetemplate import PageTemplateHandler
from server.handlers.quietstaticfilehandler import QuietStaticFileHandler
_HERE = os.path.dirname(__file__ or "")
@@ -99,11 +101,12 @@ class WebServer:
if ALLOW_SPOTTING:
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.
# API docs, Prometheus metrics, webapp manifest and static assets are always available regardless of API-only mode.
misc_routes = [
(r"/apidocs", PageTemplateHandler, {"template_name": "apidocs", **handler_opts}),
(r"/metrics", PrometheusMetricsHandler),
(r"/(.*)", StaticFileHandler, {"path": os.path.join(_HERE, "../webassets")})
(r"/manifest.webmanifest", ManifestHandler),
(r"/static/(.*)", QuietStaticFileHandler, {"path": os.path.join(_HERE, "../webassets")})
]
app = tornado.web.Application(api_routes + ui_routes + misc_routes,
@@ -5,6 +5,7 @@ from threading import Thread, Event
import pytz
import requests
from requests.exceptions import ConnectionError
from core.constants import HTTP_HEADERS
from solarconditionsproviders.ionosonde_utils import compute_band_states
@@ -4,6 +4,7 @@ from threading import Thread, Event
import pytz
import requests
from requests.exceptions import ConnectionError
from core.constants import HTTP_HEADERS
from solarconditionsproviders.solar_conditions_provider import SolarConditionsProvider
+1
View File
@@ -4,6 +4,7 @@ from threading import Thread, Event
import pytz
import requests
from requests.exceptions import ConnectionError
from core.constants import HTTP_HEADERS
from solarconditionsproviders.ionosonde_utils import compute_band_states
+1
View File
@@ -4,6 +4,7 @@ from datetime import datetime
import pytz
import requests
from requests.exceptions import ConnectionError
from core.constants import HTTP_HEADERS
from data.sig_ref import SIGRef
+1
View File
@@ -4,6 +4,7 @@ from threading import Thread, Event
import pytz
import requests
from requests.exceptions import ConnectionError
from core.constants import HTTP_HEADERS
from spotproviders.spot_provider import SpotProvider
+1
View File
@@ -2,6 +2,7 @@ import logging
from datetime import datetime
import requests
from requests.exceptions import ConnectionError
from core.constants import HTTP_HEADERS
from data.sig_ref import SIGRef
+10 -1
View File
@@ -159,13 +159,22 @@
modify it however you like, you can claim you wrote it and charge people £1000 for a copy, I don't really mind.
(Please don't do the last one. But if you're using my code for something cool, it would be nice to hear from
you!)</p>
<h2 class="mt-4">Data Accuracy</h2>
<h2 id="accuracy" class="mt-4">Data Accuracy</h2>
<p>Please note that the data coming out of Spothole is only as good as the data going in. People mis-hear and make
typos when spotting callsigns all the time. There are also plenty of cases where Spothole's data, particularly
location data, may be inaccurate. For example, there are POTA parks that span multiple US states, countries that
span multiple CQ zones, portable operators with no requirement to sign /P, etc. If you are doing something where
accuracy is important, such as contesting, you should not rely on Spothole's data to fill in any gaps in your
log.</p>
<p>In the Spothole user interface, under "Your Data", you can enter your credentials for QRZ.com and/or HamQTH if
you have them. This allows Spothole to augment its data with lookups from these services. See the following
section, Privacy, for details of how these are handled. If you are looking at the map and see lots of spots in
the geographic centre of countries, allowing these lookups will help. For QRZ.com a paid account is required to
look up operator locations.</p>
<p>IOTA in particular causes a mapping problem, firstly that there is no official set of geodata beyond the names of
islands. We also have no way of telling whether priority should be given to IOTA reference or e.g. QRZ lookup,
for example for a G callsign in EU-005 Great Britain the QRZ home address is probably more accurate, but in
EU-002 Aaland islands, it's more likely a DXpedition and the IOTA would be more accurate.</p>
<h2 id="privacy" class="mt-4">Privacy</h2>
<p>Spothole collects no data about you on a permanent basis. All spots and alerts are "timed out" and deleted from
the system after a set interval, which by default is one hour for spots and one week for alerts.</p>
+1 -1
View File
@@ -76,7 +76,7 @@
</div>
<script src="/js/add-spot.js?v=1784968398"></script>
<script src="/static/js/add-spot.js?v=1784995281"></script>
<script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -75,7 +75,7 @@
</div>
<script src="/js/alerts.js?v=1784968398"></script>
<script src="/static/js/alerts.js?v=1784995281"></script>
<script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -1,13 +1,13 @@
{% extends "skeleton.html" %}
{% block head_extra %}
<link href="/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
{% end %}
{% block body %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="text-center mb-4">
<img src="/img/logo.png" width="192" height="60" alt="Spothole">
<img src="/static/img/logo.png" width="192" height="60" alt="Spothole">
</div>
<div class="card">
<div class="card-body">
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "skeleton.html" %}
{% block body %}
<redoc spec-url="/apidocs/openapi.yml"></redoc>
<redoc spec-url="/static/apidocs/openapi.yml"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
{% end %}
+2 -2
View File
@@ -75,8 +75,8 @@
<script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script>
<script src="/js/spotsbandsandmap.js?v=1784968398"></script>
<script src="/js/bands.js?v=1784968398"></script>
<script src="/static/js/spotsbandsandmap.js?v=1784995281"></script>
<script src="/static/js/bands.js?v=1784995281"></script>
<script>$(document).ready(function () {
$("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script>
+15 -14
View File
@@ -1,26 +1,26 @@
{% extends "skeleton.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/css/style.css?v=1784968398" type="text/css">
<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/solid-6.7.2.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css?v=1784995281" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
<script src="/vendor/js/jquery-3.7.1.min.js"></script>
<script src="/vendor/js/moment-2.29.4.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="/static/vendor/js/jquery-3.7.1.min.js"></script>
<script src="/static/vendor/js/moment-2.29.4.min.js"></script>
<script src="/static/vendor/js/bootstrap-5.3.8.bundle.min.js"></script>
<script src="/static/vendor/js/tinycolor2-1.6.0.min.js"></script>
<script src="/js/utils.js?v=1784968398"></script>
<script src="/js/ui-ham.js?v=1784968398"></script>
<script src="/js/geo.js?v=1784968398"></script>
<script src="/js/common.js?v=1784968398"></script>
<script src="/static/js/utils.js?v=1784995281"></script>
<script src="/static/js/ui-ham.js?v=1784995281"></script>
<script src="/static/js/geo.js?v=1784995281"></script>
<script src="/static/js/common.js?v=1784995281"></script>
{% end %}
{% block body %}
<div class="container">
<nav id="header" class="navbar navbar-expand-lg bg-body p-0 border-bottom">
<div class="container-fluid p-0">
<a class="navbar-brand" href="/">
<img src="/img/logo.png" class="logo" width="192" height="60" alt="Spothole">
<img src="/static/img/logo.png" class="logo" width="192" height="60" alt="Spothole">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbar-toggler-content" aria-controls="navbar-toggler-content"
@@ -87,7 +87,8 @@
</div>
</div>
<div id="embeddedModeFooter" class="text-body-secondary pt-2 px-3 pb-1">Powered by <img src="/img/logo.png" class="logo"
<div id="embeddedModeFooter" class="text-body-secondary pt-2 px-3 pb-1">Powered by <img src="/static/img/logo.png"
class="logo"
width="96" height="30"
alt="Spothole"></div>
+2 -2
View File
@@ -283,8 +283,8 @@
</div>
</div>
<script src="/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/js/conditions.js?v=1784968398"></script>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1784995281"></script>
<script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script>
@@ -5,7 +5,7 @@
"short_name": "Spothole",
"scope": "/",
"display": "standalone",
"start_url": "https://spothole.app/",
"start_url": "{{ baseurl }}",
"background_color": "white",
"theme_color": "white",
"description": "An Amateur Radio spotting tool bringing together DX clusters and outdoor programmes, providing a universal JSON API and web interface.",
@@ -13,17 +13,17 @@
"prefer_related_applications": false,
"icons": [
{
"src": "/img/icon-192-pwa.png",
"src": "/static/img/icon-192-pwa.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "/img/icon-512-pwa.png",
"src": "/static/img/icon-512-pwa.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"url": "https://spothole.app"
"url": "{{ baseurl }}"
}
+29 -16
View File
@@ -1,6 +1,19 @@
{% extends "base.html" %}
{% block content %}
<div id="map-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>Spothole's map</strong><br/>Spothole pulls in location data from
a number of sources to try and provide the best map possible. However, please don't trust them to be anything
other than a rough guide. In particular, if you are seeing lots of spots in the centre of a country, please
consider opening the "Your Data" menu and providing QRZ.com and/or HamQTH credentials so that Spothole can find
more accurate locations for these spots. More information can be found in the
<a href="/about#accuracy" class="alert-link">Data Accuracy section of the About page</a>.
<button type="button" id="map-intro-box-dismiss" class="btn-close" data-bs-dismiss="alert"
aria-label="Close"></button>
</div>
</div>
<div id="map">
<div id="settingsButtonRowMap" class="mt-3 px-3">
<div class="row mb-3">
@@ -77,26 +90,26 @@
</div>
</div>
<link rel="stylesheet" href="/vendor/css/leaflet-1.9.4.min.css">
<link rel="stylesheet" href="/vendor/css/leaflet-extra-markers-1.2.2.min.css">
<script src="/vendor/js/leaflet-1.9.4.min.js"></script>
<script src="/vendor/js/oms-leaflet-0.2.7.min.js"></script>
<script src="/vendor/js/leaflet-providers-2.0.0.js"></script>
<script src="/vendor/js/leaflet-extra-markers-1.2.2.min.js"></script>
<script src="/vendor/js/leaflet-geodesic-2.7.2.umd.min.js"></script>
<script src="/vendor/js/leaflet-vectorgrid-1.3.0.js"></script>
<script src="/vendor/js/text-image-0.7.0.js"></script>
<script src="/vendor/js/leaflet-terminator-1.1.0.min.js"></script>
<script src="/vendor/js/leaflet-maidenhead.js"></script>
<script src="/vendor/js/leaflet-ituzones.js"></script>
<script src="/vendor/js/leaflet-cqzones.js"></script>
<script src="/vendor/js/leaflet-workedallbritainireland.js"></script>
<link rel="stylesheet" href="/static/vendor/css/leaflet-1.9.4.min.css">
<link rel="stylesheet" href="/static/vendor/css/leaflet-extra-markers-1.2.2.min.css">
<script src="/static/vendor/js/leaflet-1.9.4.min.js"></script>
<script src="/static/vendor/js/oms-leaflet-0.2.7.min.js"></script>
<script src="/static/vendor/js/leaflet-providers-2.0.0.js"></script>
<script src="/static/vendor/js/leaflet-extra-markers-1.2.2.min.js"></script>
<script src="/static/vendor/js/leaflet-geodesic-2.7.2.umd.min.js"></script>
<script src="/static/vendor/js/leaflet-vectorgrid-1.3.0.js"></script>
<script src="/static/vendor/js/text-image-0.7.0.js"></script>
<script src="/static/vendor/js/leaflet-terminator-1.1.0.min.js"></script>
<script src="/static/vendor/js/leaflet-maidenhead.js"></script>
<script src="/static/vendor/js/leaflet-ituzones.js"></script>
<script src="/static/vendor/js/leaflet-cqzones.js"></script>
<script src="/static/vendor/js/leaflet-workedallbritainireland.js"></script>
<script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script>
<script src="/js/spotsbandsandmap.js?v=1784968398"></script>
<script src="/js/map.js?v=1784968398"></script>
<script src="/static/js/spotsbandsandmap.js?v=1784995281"></script>
<script src="/static/js/map.js?v=1784995281"></script>
<script>$(document).ready(function () {
$("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script>
+11 -11
View File
@@ -15,10 +15,10 @@
content="An Amateur Radio spotting tool bringing together DX clusters and outdoor programmes, providing a universal JSON API and web interface."/>
<meta property="og:description"
content="An Amateur Radio spotting tool bringing together DX clusters and outdoor programmes, providing a universal JSON API and web interface."/>
<link rel="canonical" href="https://spothole.app/"/>
<meta property="og:url" content="https://spothole.app/"/>
<meta property="og:image" content="https://spothole.app/img/banner.png"/>
<meta property="twitter:image" content="https://spothole.app/img/banner.png"/>
<link rel="canonical" href="{{ baseurl }}"/>
<meta property="og:url" content="{{ baseurl }}"/>
<meta property="og:image" content="{{ baseurl }}/static/img/banner.png"/>
<meta property="twitter:image" content="{{ baseurl }}/static/img/banner.png"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="author" content="Ian Renton"/>
<meta property="og:locale" content="en_GB"/>
@@ -26,14 +26,14 @@
<title>Spothole</title>
<link rel="icon" type="image/png" href="/img/icon-512.png">
<link rel="apple-touch-icon" href="img/icon-512-pwa.png">
<link rel="alternate icon" type="image/png" href="/img/icon-192.png">
<link rel="alternate icon" type="image/png" href="/img/icon-32.png">
<link rel="alternate icon" type="image/png" href="/img/icon-16.png">
<link rel="alternate icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" href="/static/img/icon-512.png">
<link rel="apple-touch-icon" href="/static/img/icon-512-pwa.png">
<link rel="alternate icon" type="image/png" href="/static/img/icon-192.png">
<link rel="alternate icon" type="image/png" href="/static/img/icon-32.png">
<link rel="alternate icon" type="image/png" href="/static/img/icon-16.png">
<link rel="alternate icon" type="image/x-icon" href="/static/favicon.ico">
<link rel="manifest" href="manifest.webmanifest">
<link rel="manifest" href="/manifest.webmanifest">
{% block head_extra %}{% end %}
</head>
+2 -2
View File
@@ -116,8 +116,8 @@
<script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script>
<script src="/js/spotsbandsandmap.js?v=1784968398"></script>
<script src="/js/spots.js?v=1784968398"></script>
<script src="/static/js/spotsbandsandmap.js?v=1784995281"></script>
<script src="/static/js/spots.js?v=1784995281"></script>
<script>$(document).ready(function () {
$("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -59,7 +59,7 @@
</div>
</div>
<script src="/js/status.js?v=1784968398"></script>
<script src="/static/js/status.js?v=1784995281"></script>
<script>
$(document).ready(function () {
$("#nav-link-status").addClass("active");
+1 -1
View File
@@ -184,7 +184,7 @@ function addAlertRowsToTable(tbody, alerts) {
// Format DX flag
let dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (a["dx_dxcc_id"] && a["dx_dxcc_id"] != null && a["dx_dxcc_id"] !== 0) {
dx_flag = `<img src="img/flags/${a['dx_dxcc_id']}.png" class="flag" width="24" alt="${dx_country}" title="${dx_country}"/>`;
dx_flag = `<img src="static/img/flags/${a['dx_dxcc_id']}.png" class="flag" width="24" alt="${dx_country}" title="${dx_country}"/>`;
}
// Format dx calls
+34 -4
View File
@@ -38,8 +38,13 @@ function loadSpots() {
if (evtSource != null) {
evtSource.close();
}
// On first fetch, don't include QRZ/HamQTH credentials. This will cause some inaccurate
// marker positions but avoids having to wait a minute or more for all the lookups to fire.
// Including QRZ/HamQTH lookups to improve positions causes the load to be really slow, so on first load
// the user would be waiting ages without data and will think it's broken. We therefore load in several
// stages:
// 1) Load without any credentials
// 2) (If we have credentials) reload with them, replacing what's already there,
// 3) Subscribe to the SSE endpoint (with credentials if we have them) so that updates come with augmented
// data if they can.
$.getJSON('/api/v1/spots' + buildQueryString(false), function (jsonData) {
// Store data
spots = jsonData;
@@ -48,8 +53,21 @@ function loadSpots() {
if ($("#showTerminator")[0].checked) {
terminator.setTime();
}
// Start the ongoing SSE connection
startSSEConnection();
// Check if we have any credentials to use
if (getCredentialQueryString() !== "") {
// OK, we have credentials and have loaded once without them so the user has a basic map. Now reload
// with the credentials and replace what's on the map, so we can improve the data.
$.getJSON('/api/v1/spots' + buildQueryString(true), function (jsonData2) {
spots = jsonData2;
updateMap();
// Now start the ongoing SSE connection
startSSEConnection();
});
} else {
// We had no credentials with which to augment the data anyway, so just start the SSE connection
// now
startSSEConnection();
}
});
}
@@ -574,6 +592,16 @@ function setUpMap() {
map.setView([30, 0], 3);
}
// Display the intro box, unless the user has already dismissed it once.
function displayIntroBox() {
if (localStorage.getItem("map-intro-box-dismissed") == null) {
$("#map-intro-box").show();
}
$("#map-intro-box-dismiss").click(function () {
localStorage.setItem("map-intro-box-dismissed", true);
});
}
// Startup
$(document).ready(function () {
// Close SSE connection cleanly when navigating away
@@ -589,6 +617,8 @@ $(document).ready(function () {
setUpMap();
// Call loadOptions(), this will then trigger loading spots and setting up timers.
loadOptions();
// Display intro box
displayIntroBox();
// Prevent mouse scroll and touch actions in the popup menus being passed through to the map
L.DomEvent.disableScrollPropagation(document.getElementById('settingsButtonRowMap'));
L.DomEvent.disableClickPropagation(document.getElementById('settingsButtonRowMap'));
+2 -2
View File
@@ -249,7 +249,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
dx_flag = "";
}
if (s["dx_dxcc_id"] && s["dx_dxcc_id"] != null && s["dx_dxcc_id"] !== 0) {
dx_flag = `<img src="img/flags/${s['dx_dxcc_id']}.png" class="flag" width="24" alt="${dx_country}" title="${dx_country}"/>`;
dx_flag = `<img src="static/img/flags/${s['dx_dxcc_id']}.png" class="flag" width="24" alt="${dx_country}" title="${dx_country}"/>`;
}
// Format the frequency
@@ -319,7 +319,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
// Format DE flag
let de_flag = "<i class='fa-solid fa-circle-question'></i>";
if (s["de_dxcc_id"] && s["de_dxcc_id"] != null && s["de_dxcc_id"] !== 0) {
de_flag = `<img src="img/flags/${s['de_dxcc_id']}.png" class="flag" width="24" alt="${de_country}" title="${de_country}"/>`;
de_flag = `<img src="static/img/flags/${s['de_dxcc_id']}.png" class="flag" width="24" alt="${de_country}" title="${de_country}"/>`;
}
// Format de call
+52 -23
View File
@@ -1,28 +1,57 @@
const CACHE_NAME = 'Spothole';
const CACHE_URLS = [
'index.html',
'./',
'apidocs',
'apidocs/openapi.yml',
'about',
'css/style.css',
'js/add-spot.js',
'js/alerts.js',
'js/bands.js',
'js/common.js',
'js/map.js',
'js/spots.js',
'js/spotsbandsandmap.js',
'js/status.js',
'img/logo.png',
'img/favicon.ico',
'img/icon-32.png',
'img/icon-192.png',
'img/icon-512.png',
'fa/css/fontawesome.min.css',
'fa/css/solid.min.css',
'fa/webfonts/fa-solid-900.ttf',
'fa/webfonts/fa-solid-900.woff2'
'static/apidocs/openapi.yml',
'static/audio/ping.mp3',
'static/css/style.css',
'static/js/add-spot.js',
'static/js/alerts.js',
'static/js/bands.js',
'static/js/common.js',
'static/js/geo.js',
'static/js/map.js',
'static/js/spots.js',
'static/js/spotsbandsandmap.js',
'static/js/status.js',
'static/js/ui-ham.js',
'static/js/utils.js',
'static/img/logo.png',
'static/img/favicon.ico',
'static/img/icon-32.png',
'static/img/icon-192.png',
'static/img/icon-512.png',
'static/vendor/img/markers_default@2x.png',
'static/vendor/img/markers_shadow.png',
'static/vendor/img/markers_default.png',
'static/vendor/img/markers_shadow@2x.png',
'static/vendor/css/bootstrap-5.3.8.min.css',
'static/vendor/css/images/marker-icon.png',
'static/vendor/css/images/layers-2x.png',
'static/vendor/css/images/marker-shadow.png',
'static/vendor/css/images/layers.png',
'static/vendor/css/images/marker-icon-2x.png',
'static/vendor/css/leaflet-extra-markers-1.2.2.min.css',
'static/vendor/css/leaflet-1.9.4.min.css',
'static/vendor/css/fontawesome-6.7.2.min.css',
'static/vendor/css/solid-6.7.2.min.css',
'static/vendor/js/jquery-3.7.1.min.js',
'static/vendor/js/chart-4.4.9.umd.min.js',
'static/vendor/js/oms-leaflet-0.2.7.min.js',
'static/vendor/js/leaflet-workedallbritainireland.js',
'static/vendor/js/leaflet-providers-2.0.0.js',
'static/vendor/js/text-image-0.7.0.js',
'static/vendor/js/leaflet-vectorgrid-1.3.0.js',
'static/vendor/js/leaflet-ituzones.js',
'static/vendor/js/tinycolor2-1.6.0.min.js',
'static/vendor/js/bootstrap-5.3.8.bundle.min.js',
'static/vendor/js/moment-2.29.4.min.js',
'static/vendor/js/leaflet-cqzones.js',
'static/vendor/js/leaflet-extra-markers-1.2.2.min.js',
'static/vendor/js/leaflet-terminator-1.1.0.min.js',
'static/vendor/js/leaflet-1.9.4.min.js',
'static/vendor/js/leaflet-geodesic-2.7.2.umd.min.js',
'static/vendor/js/leaflet-maidenhead.js',
'static/vendor/webfonts/fa-solid-900.ttf',
'static/vendor/webfonts/fa-solid-900.woff2'
];
self.addEventListener('fetch', (event) => {