mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-03-15 20:34:31 +00:00
Compare commits
5 Commits
220c9378cf
...
1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
059d9364eb | ||
|
|
a3ca590ca3 | ||
|
|
cfff8dd832 | ||
|
|
d1a5bfe9c3 | ||
|
|
da2827f559 |
@@ -60,10 +60,15 @@ spot-providers:
|
|||||||
host: "hrd.wa9pie.net"
|
host: "hrd.wa9pie.net"
|
||||||
port: 8000
|
port: 8000
|
||||||
# Prompt the cluster node gives when asking for a callsign to log in. Varies between cluster node software.
|
# Prompt the cluster node gives when asking for a callsign to log in. Varies between cluster node software.
|
||||||
login_prompt: "login: "
|
login_prompt: "login:"
|
||||||
# Callsign Spothole will use to log into this cluster. Ensure the SSID (e.g. -99) is different to any personal
|
# Callsign Spothole will use to log into this cluster. Ensure the SSID (e.g. -99) is different to any personal
|
||||||
# connection you might make to this cluster node.
|
# connection you might make to this cluster node.
|
||||||
login_callsign: "N0CALL-99"
|
login_callsign: "N0CALL-99"
|
||||||
|
# Whether to allow RBN spots that come via this cluster. If you don't want RBN spots or you are making a separate
|
||||||
|
# connection to RBN directly, leave this as False. If you want RBN spots from this cluster, set this to True. (Make
|
||||||
|
# sure you aren't also separately connecting to RBN directly, otherwise you may get duplicate spots.) Note that not
|
||||||
|
# all clusters sent RBN spots anyway.
|
||||||
|
allow_rbn_spots: false
|
||||||
-
|
-
|
||||||
class: "DXCluster"
|
class: "DXCluster"
|
||||||
name: "W3LPL Cluster"
|
name: "W3LPL Cluster"
|
||||||
@@ -71,24 +76,36 @@ spot-providers:
|
|||||||
host: "w3lpl.net"
|
host: "w3lpl.net"
|
||||||
port: 7373
|
port: 7373
|
||||||
# Prompt the cluster node gives when asking for a callsign to log in. Varies between cluster node software.
|
# Prompt the cluster node gives when asking for a callsign to log in. Varies between cluster node software.
|
||||||
login_prompt: "Please enter your call: "
|
login_prompt: "Please enter your call:"
|
||||||
# Callsign Spothole will use to log into this cluster. Ensure the SSID (e.g. -99) is different to any personal
|
# Callsign Spothole will use to log into this cluster. Ensure the SSID (e.g. -99) is different to any personal
|
||||||
# connection you might make to this cluster node.
|
# connection you might make to this cluster node.
|
||||||
login_callsign: "N0CALL-99"
|
login_callsign: "N0CALL-99"
|
||||||
|
# Whether to allow RBN spots that come via this cluster. If you don't want RBN spots or you are making a separate
|
||||||
|
# connection to RBN directly, leave this as False. If you want RBN spots from this cluster, set this to True. (Make
|
||||||
|
# sure you aren't also separately connecting to RBN directly, otherwise you may get duplicate spots.) Note that not
|
||||||
|
# all clusters sent RBN spots anyway.
|
||||||
|
allow_rbn_spots: false
|
||||||
-
|
-
|
||||||
class: "RBN"
|
class: "RBN"
|
||||||
name: "RBN CW/RTTY"
|
name: "RBN CW/RTTY"
|
||||||
enabled: false
|
enabled: false
|
||||||
port: 7000
|
port: 7000
|
||||||
|
# This setting doesn't affect the spot provider itself, or anything in the back-end of Spothole, just the web UI.
|
||||||
|
# By default spots from all enabled providers will be shown in the web UI. However, you might want RBN data to be
|
||||||
|
# received by Spothole but not shown on the web UI unless the user explicitly turns it on. For that behaviour,
|
||||||
|
# set enabled to true, but enabled-by-default-in-web-ui to false.
|
||||||
|
enabled-by-default-in-web-ui: false
|
||||||
-
|
-
|
||||||
class: "RBN"
|
class: "RBN"
|
||||||
name: "RBN FT8"
|
name: "RBN FT8"
|
||||||
enabled: false
|
enabled: false
|
||||||
port: 7001
|
port: 7001
|
||||||
|
enabled-by-default-in-web-ui: false
|
||||||
-
|
-
|
||||||
class: "UKPacketNet"
|
class: "UKPacketNet"
|
||||||
name: "UK Packet Radio Net"
|
name: "UK Packet Radio Net"
|
||||||
enabled: false
|
enabled: false
|
||||||
|
enabled-by-default-in-web-ui: false
|
||||||
-
|
-
|
||||||
class: "XOTA"
|
class: "XOTA"
|
||||||
name: "39C3 TOTA"
|
name: "39C3 TOTA"
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import yaml
|
|||||||
|
|
||||||
# Check you have a config file
|
# Check you have a config file
|
||||||
if not os.path.isfile("config.yml"):
|
if not os.path.isfile("config.yml"):
|
||||||
logging.error("Your config file is missing. Ensure you have copied config-example.yml to config.yml and updated it according to your needs.")
|
logging.error(
|
||||||
|
"Your config file is missing. Ensure you have copied config-example.yml to config.yml and updated it according to your needs.")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
# Load config
|
# Load config
|
||||||
@@ -18,3 +19,8 @@ SERVER_OWNER_CALLSIGN = config["server-owner-callsign"]
|
|||||||
WEB_SERVER_PORT = config["web-server-port"]
|
WEB_SERVER_PORT = config["web-server-port"]
|
||||||
ALLOW_SPOTTING = config["allow-spotting"]
|
ALLOW_SPOTTING = config["allow-spotting"]
|
||||||
WEB_UI_OPTIONS = config["web-ui-options"]
|
WEB_UI_OPTIONS = config["web-ui-options"]
|
||||||
|
|
||||||
|
# For ease of config, each spot provider owns its own config about whether it should be enabled by default in the web UI
|
||||||
|
# but for consistency we provide this to the front-end in web-ui-options because it has no impact outside of the web UI.
|
||||||
|
WEB_UI_OPTIONS["spot-providers-enabled-by-default"] = [p["name"] for p in config["spot-providers"] if p["enabled"] and (
|
||||||
|
"enabled-by-default-in-web-ui" not in p or p["enabled-by-default-in-web-ui"] == True)]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from data.sig import SIG
|
|||||||
|
|
||||||
# General software
|
# General software
|
||||||
SOFTWARE_NAME = "Spothole by M0TRT"
|
SOFTWARE_NAME = "Spothole by M0TRT"
|
||||||
SOFTWARE_VERSION = "1.1-pre"
|
SOFTWARE_VERSION = "1.1"
|
||||||
|
|
||||||
# HTTP headers used for spot providers that use HTTP
|
# HTTP headers used for spot providers that use HTTP
|
||||||
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + ", v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + ", v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
|||||||
# one of our proviers.
|
# one of our proviers.
|
||||||
if ALLOW_SPOTTING:
|
if ALLOW_SPOTTING:
|
||||||
options["spot_sources"].append("API")
|
options["spot_sources"].append("API")
|
||||||
|
options["web-ui-options"]["spot-providers-enabled-by-default"].append("API")
|
||||||
|
|
||||||
self.write(json.dumps(options, default=serialize_everything))
|
self.write(json.dumps(options, default=serialize_everything))
|
||||||
self.set_status(200)
|
self.set_status(200)
|
||||||
|
|||||||
@@ -7,27 +7,32 @@ from time import sleep
|
|||||||
import pytz
|
import pytz
|
||||||
import telnetlib3
|
import telnetlib3
|
||||||
|
|
||||||
|
from core.config import SERVER_OWNER_CALLSIGN
|
||||||
from data.spot import Spot
|
from data.spot import Spot
|
||||||
from spotproviders.spot_provider import SpotProvider
|
from spotproviders.spot_provider import SpotProvider
|
||||||
|
|
||||||
|
|
||||||
# Spot provider for a DX Cluster. Hostname, port, login_prompt and login_callsign are provided in config.
|
# Spot provider for a DX Cluster. Hostname, port, login_prompt, login_callsign and allow_rbn_spots are provided in config.
|
||||||
|
# See config-example.yml for examples.
|
||||||
class DXCluster(SpotProvider):
|
class DXCluster(SpotProvider):
|
||||||
# Note the callsign pattern deliberately excludes calls ending in "-#", which are from RBN and can be enabled by
|
|
||||||
# default on some clusters. If you want RBN spots, there is a separate provider for that.
|
|
||||||
CALLSIGN_PATTERN = "([a-z|0-9|/]+)"
|
CALLSIGN_PATTERN = "([a-z|0-9|/]+)"
|
||||||
FREQUENCY_PATTERN = "([0-9|.]+)"
|
FREQUENCY_PATTERN = "([0-9|.]+)"
|
||||||
LINE_PATTERN = re.compile(
|
LINE_PATTERN_EXCLUDE_RBN = re.compile(
|
||||||
"^DX de " + CALLSIGN_PATTERN + ":\\s+" + FREQUENCY_PATTERN + "\\s+" + CALLSIGN_PATTERN + "\\s+(.*)\\s+(\\d{4}Z)",
|
"^DX de " + CALLSIGN_PATTERN + ":\\s+" + FREQUENCY_PATTERN + "\\s+" + CALLSIGN_PATTERN + "\\s+(.*)\\s+(\\d{4}Z)",
|
||||||
re.IGNORECASE)
|
re.IGNORECASE)
|
||||||
|
LINE_PATTERN_ALLOW_RBN = re.compile(
|
||||||
|
"^DX de " + CALLSIGN_PATTERN + "-?#?:\\s+" + FREQUENCY_PATTERN + "\\s+" + CALLSIGN_PATTERN + "\\s+(.*)\\s+(\\d{4}Z)",
|
||||||
|
re.IGNORECASE)
|
||||||
|
|
||||||
# Constructor requires hostname and port
|
# Constructor requires hostname and port
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
super().__init__(provider_config)
|
super().__init__(provider_config)
|
||||||
self.hostname = provider_config["host"]
|
self.hostname = provider_config["host"]
|
||||||
self.port = provider_config["port"]
|
self.port = provider_config["port"]
|
||||||
self.login_prompt = provider_config["login_prompt"]
|
self.login_prompt = provider_config["login_prompt"] if "login_prompt" in provider_config else "login:"
|
||||||
self.login_callsign = provider_config["login_callsign"]
|
self.login_callsign = provider_config["login_callsign"] if "login_callsign" in provider_config else SERVER_OWNER_CALLSIGN
|
||||||
|
self.allow_rbn_spots = provider_config["allow_rbn_spots"] if "allow_rbn_spots" in provider_config else False
|
||||||
|
self.spot_line_pattern = self.LINE_PATTERN_ALLOW_RBN if self.allow_rbn_spots else self.LINE_PATTERN_EXCLUDE_RBN
|
||||||
self.telnet = None
|
self.telnet = None
|
||||||
self.thread = Thread(target=self.handle)
|
self.thread = Thread(target=self.handle)
|
||||||
self.thread.daemon = True
|
self.thread.daemon = True
|
||||||
@@ -63,7 +68,7 @@ class DXCluster(SpotProvider):
|
|||||||
try:
|
try:
|
||||||
# Check new telnet info against regular expression
|
# Check new telnet info against regular expression
|
||||||
telnet_output = self.telnet.read_until("\n".encode("latin-1"))
|
telnet_output = self.telnet.read_until("\n".encode("latin-1"))
|
||||||
match = self.LINE_PATTERN.match(telnet_output.decode("latin-1"))
|
match = self.spot_line_pattern.match(telnet_output.decode("latin-1"))
|
||||||
if match:
|
if match:
|
||||||
spot_time = datetime.strptime(match.group(5), "%H%MZ")
|
spot_time = datetime.strptime(match.group(5), "%H%MZ")
|
||||||
spot_datetime = datetime.combine(datetime.today(), spot_time.time()).replace(tzinfo=pytz.UTC)
|
spot_datetime = datetime.combine(datetime.today(), spot_time.time()).replace(tzinfo=pytz.UTC)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<p>This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.</p>
|
<p>This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script src="/js/add-spot.js?v=5"></script>
|
<script src="/js/add-spot.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -168,8 +168,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script src="/js/alerts.js?v=5"></script>
|
<script src="/js/alerts.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -134,9 +134,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=5"></script>
|
<script src="/js/spotsbandsandmap.js?v=6"></script>
|
||||||
<script src="/js/bands.js?v=5"></script>
|
<script src="/js/bands.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -46,10 +46,10 @@
|
|||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/cjs/tinycolor.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/cjs/tinycolor.min.js"></script>
|
||||||
|
|
||||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=5"></script>
|
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=6"></script>
|
||||||
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=5"></script>
|
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=6"></script>
|
||||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=5"></script>
|
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=6"></script>
|
||||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=5"></script>
|
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=6"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -152,9 +152,9 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/leaflet.geodesic"></script>
|
<script src="https://cdn.jsdelivr.net/npm/leaflet.geodesic"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@joergdietrich/leaflet.terminator@1.1.0/L.Terminator.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@joergdietrich/leaflet.terminator@1.1.0/L.Terminator.min.js"></script>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=5"></script>
|
<script src="/js/spotsbandsandmap.js?v=6"></script>
|
||||||
<script src="/js/map.js?v=5"></script>
|
<script src="/js/map.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -223,9 +223,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=5"></script>
|
<script src="/js/spotsbandsandmap.js?v=6"></script>
|
||||||
<script src="/js/spots.js?v=5"></script>
|
<script src="/js/spots.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
|
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=5"></script>
|
<script src="/js/common.js?v=6"></script>
|
||||||
<script src="/js/status.js?v=5"></script>
|
<script src="/js/status.js?v=6"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
10
webassets/.idea/.gitignore
generated
vendored
Normal file
10
webassets/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
6
webassets/.idea/vcs.xml
generated
Normal file
6
webassets/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
openapi: 3.0.4
|
openapi: 3.1.0
|
||||||
info:
|
info:
|
||||||
title: Spothole API
|
title: Spothole API
|
||||||
description: |-
|
description: |-
|
||||||
@@ -557,6 +557,12 @@ paths:
|
|||||||
type: integer
|
type: integer
|
||||||
example: 30
|
example: 30
|
||||||
description: The suggested default "maximum spot age" that the web UI should retrieve from the API
|
description: The suggested default "maximum spot age" that the web UI should retrieve from the API
|
||||||
|
spot-providers-enabled-by-default:
|
||||||
|
type: array
|
||||||
|
description: A list of the spot providers that should be enabled in the web UI on first load, if the user hasn't already got a localStorage setting that sets their preference. This is to allow some high-volume providers like RBN to be enabled in Spothole's back-end and displayable in the web UI if the user wants, but by default the experience will not include them.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: "POTA"
|
||||||
alert-count:
|
alert-count:
|
||||||
type: array
|
type: array
|
||||||
description: An array of suggested "alert counts" that the web UI can retrieve from the API
|
description: An array of suggested "alert counts" that the web UI can retrieve from the API
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
generateSourcesMultiToggleFilterCard(options["spot_sources"], options["web-ui-options"]["spot-providers-enabled-by-default"]);
|
||||||
|
|
||||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
generateSourcesMultiToggleFilterCard(options["spot_sources"], options["web-ui-options"]["spot-providers-enabled-by-default"]);
|
||||||
|
|
||||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ function loadOptions() {
|
|||||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||||
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
|
||||||
generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);
|
generateSourcesMultiToggleFilterCard(options["spot_sources"], options["web-ui-options"]["spot-providers-enabled-by-default"]);
|
||||||
|
|
||||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ var spots = []
|
|||||||
function addBandToggleColourCSS(band_options) {
|
function addBandToggleColourCSS(band_options) {
|
||||||
var $style = $('<style>');
|
var $style = $('<style>');
|
||||||
band_options.forEach(o => {
|
band_options.forEach(o => {
|
||||||
// CSS doesn't like IDs with decimal points in, so we need to replace that
|
var domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||||
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
|
$style.append(`#filter-button-label-band-${domSafeName} { border-color: ${bandToColor(o['name'])}; color: var(--bs-primary);}`);
|
||||||
$style.append(`#filter-button-label-band-${cssFormattedBandName} { border-color: ${bandToColor(o['name'])}; color: var(--bs-primary);}`);
|
$style.append(`.btn-check:checked + #filter-button-label-band-${domSafeName} { background-color: ${bandToColor(o['name'])}; color: ${bandToContrastColor(o['name'])};}`);
|
||||||
$style.append(`.btn-check:checked + #filter-button-label-band-${cssFormattedBandName} { background-color: ${bandToColor(o['name'])}; color: ${bandToContrastColor(o['name'])};}`);
|
|
||||||
});
|
});
|
||||||
$('html > head').append($style);
|
$('html > head').append($style);
|
||||||
}
|
}
|
||||||
@@ -18,10 +17,8 @@ function addBandToggleColourCSS(band_options) {
|
|||||||
function generateBandsMultiToggleFilterCard(band_options) {
|
function generateBandsMultiToggleFilterCard(band_options) {
|
||||||
// Create a button for each option
|
// Create a button for each option
|
||||||
band_options.forEach(o => {
|
band_options.forEach(o => {
|
||||||
// CSS doesn't like IDs with decimal points in, so we need to replace that in the same way as when we originally
|
var domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||||
// queried the options endpoint and set our CSS.
|
$("#band-options").append(`<input type="checkbox" class="btn-check filter-button-band storeable-checkbox" name="options" id="filter-button-band-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline" id="filter-button-label-band-${domSafeName}" for="filter-button-band-${domSafeName}">${o['name']}</label> `);
|
||||||
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
|
|
||||||
$("#band-options").append(`<input type="checkbox" class="btn-check filter-button-band storeable-checkbox" name="options" id="filter-button-band-${cssFormattedBandName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline" id="filter-button-label-band-${cssFormattedBandName}" for="filter-button-band-${cssFormattedBandName}">${o['name']}</label> `);
|
|
||||||
});
|
});
|
||||||
// Create All/None/Ham HF buttons
|
// Create All/None/Ham HF buttons
|
||||||
$("#band-options").append(` <span style="display: inline-block"><button id="filter-button-band-all" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('band', true);">All</button> <button id="filter-button-band-none" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('band', false);">None</button> <button id="filter-button-band-none" type="button" class="btn btn-outline-secondary" onclick="setHamHFBandToggles();">Ham HF</button></span>`);
|
$("#band-options").append(` <span style="display: inline-block"><button id="filter-button-band-all" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('band', true);">All</button> <button id="filter-button-band-none" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('band', false);">None</button> <button id="filter-button-band-none" type="button" class="btn btn-outline-secondary" onclick="setHamHFBandToggles();">Ham HF</button></span>`);
|
||||||
@@ -41,7 +38,8 @@ function setHamHFBandToggles() {
|
|||||||
function generateSIGsMultiToggleFilterCard(sig_options) {
|
function generateSIGsMultiToggleFilterCard(sig_options) {
|
||||||
// Create a button for each option
|
// Create a button for each option
|
||||||
sig_options.forEach(o => {
|
sig_options.forEach(o => {
|
||||||
$("#sig-options").append(`<input type="checkbox" class="btn-check filter-button-sig storeable-checkbox" name="options" id="filter-button-sig-${o['name']}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline-primary" id="filter-button-label-sig-${o['name']}" for="filter-button-sig-${o['name']}" title="${o['description']}"><i class="fa-solid ${sigToIcon(o['name'], 'fa-tower-cell')}"></i> ${o['name']}</label> `);
|
var domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||||
|
$("#sig-options").append(`<input type="checkbox" class="btn-check filter-button-sig storeable-checkbox" name="options" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline-primary" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${sigToIcon(o['name'], 'fa-tower-cell')}"></i> ${o['name']}</label> `);
|
||||||
});
|
});
|
||||||
// Create a bonus "NO_SIG" / "General DX" option
|
// Create a bonus "NO_SIG" / "General DX" option
|
||||||
$("#sig-options").append(`<input type="checkbox" class="btn-check filter-button-sig storeable-checkbox" name="options" id="filter-button-sig-NO_SIG" value="NO_SIG" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline-primary" id="filter-button-label-sig-NO_SIG" for="filter-button-sig-NO_SIG"><i class="fa-solid fa-tower-cell"></i> General DX</label> `);
|
$("#sig-options").append(`<input type="checkbox" class="btn-check filter-button-sig storeable-checkbox" name="options" id="filter-button-sig-NO_SIG" value="NO_SIG" autocomplete="off" onClick="filtersUpdated()" checked><label class="btn btn-outline-primary" id="filter-button-label-sig-NO_SIG" for="filter-button-sig-NO_SIG"><i class="fa-solid fa-tower-cell"></i> General DX</label> `);
|
||||||
@@ -49,6 +47,20 @@ function generateSIGsMultiToggleFilterCard(sig_options) {
|
|||||||
$("#sig-options").append(` <span style="display: inline-block"><button id="filter-button-sig-all" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('sig', true);">All</button> <button id="filter-button-sig-none" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('sig', false);">None</button></span>`);
|
$("#sig-options").append(` <span style="display: inline-block"><button id="filter-button-sig-all" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('sig', true);">All</button> <button id="filter-button-sig-none" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('sig', false);">None</button></span>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate Sources filter card. This one is a minor special case as we create the buttons in the normal way, but then
|
||||||
|
// set which ones are enabled by default based on config rather than having them all enabled by default. We also sanitise
|
||||||
|
// names here for HTML elements.
|
||||||
|
function generateSourcesMultiToggleFilterCard(source_options, sources_enabled_by_default) {
|
||||||
|
// Create a button for each option
|
||||||
|
source_options.forEach(o => {
|
||||||
|
var enable = sources_enabled_by_default.includes(o);
|
||||||
|
var domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||||
|
$("#source-options").append(`<input type="checkbox" class="btn-check filter-button-source storeable-checkbox" name="options" id="filter-button-source-${domSafeName}" value="${o}" autocomplete="off" onClick="filtersUpdated()" ${enable ? "checked" : ""}><label class="btn btn-outline-primary" for="filter-button-source-${domSafeName}">${o}</label> `);
|
||||||
|
});
|
||||||
|
// Create All/None buttons
|
||||||
|
$("#source-options").append(` <span style="display: inline-block"><button id="filter-button-source-all" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('source', true);">All</button> <button id="filter-button-source-none" type="button" class="btn btn-outline-secondary" onclick="toggleFilterButtons('source', false);">None</button></span>`);
|
||||||
|
}
|
||||||
|
|
||||||
// Method called when any filter is changed to reload the spots and persist the filter settings.
|
// Method called when any filter is changed to reload the spots and persist the filter settings.
|
||||||
function filtersUpdated() {
|
function filtersUpdated() {
|
||||||
loadSpots();
|
loadSpots();
|
||||||
|
|||||||
Reference in New Issue
Block a user