mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-08-05 18:11:41 +00:00
Compare commits
8
Commits
ba08555a30
...
ecab8d6d16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecab8d6d16 | ||
|
|
0cea777616 | ||
|
|
3c4c595288 | ||
|
|
555a8418a9 | ||
|
|
7ee08e2fcd | ||
|
|
4353c9ebf9 | ||
|
|
4a9ff2c636 | ||
|
|
e7253c0916 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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 %}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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'));
|
||||
|
||||
@@ -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
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user