Modify the front so that it allows QRZ.com and HamQTH credentials to be provided by the client (if none are provided, the lookups do not occur.)

This commit is contained in:
Ian Renton
2026-05-09 16:52:48 +01:00
parent f81ef4347f
commit 1ef8b36cb1
18 changed files with 224 additions and 46 deletions

View File

@@ -28,6 +28,7 @@
<p>Spothole can retrieve spots from: <a href="https://www.dxcluster.info/telnet/">Telnet-based DX clusters</a>, the <a href="https://www.reversebeacon.net/">Reverse Beacon Network (RBN)</a>, the <a href="https://www.aprs-is.net/">APRS Internet Service (APRS-IS)</a>, <a href="https://pota.app">POTA</a>, <a href="https://www.sota.org.uk/">SOTA</a>, <a href="https://wwff.co/">WWFF</a>, <a href="https://www.cqgma.org/">GMA</a>, <a href="https://wwbota.net/">WWBOTA</a>, <a href="http://www.hema.org.uk/">HEMA</a>, <a href="https://www.parksnpeaks.org/">Parks 'n' Peaks</a>, <a href="https://ontheair.nz">ZLOTA</a>, <a href="https://www.wota.org.uk/">WOTA</a>, <a href="https://llota.app">LLOTA</a>, <a href="https://wwtota.com">WWTOTA</a>, <a href="https://tilesontheair.com/">Tiles on the Air</a>, the <a href="https://ukpacketradio.network/">UK Packet Repeater Network</a>, and any site based on the <a href="https://github.com/nischu/xOTA">xOTA software by nischu</a>.</p> <p>Spothole can retrieve spots from: <a href="https://www.dxcluster.info/telnet/">Telnet-based DX clusters</a>, the <a href="https://www.reversebeacon.net/">Reverse Beacon Network (RBN)</a>, the <a href="https://www.aprs-is.net/">APRS Internet Service (APRS-IS)</a>, <a href="https://pota.app">POTA</a>, <a href="https://www.sota.org.uk/">SOTA</a>, <a href="https://wwff.co/">WWFF</a>, <a href="https://www.cqgma.org/">GMA</a>, <a href="https://wwbota.net/">WWBOTA</a>, <a href="http://www.hema.org.uk/">HEMA</a>, <a href="https://www.parksnpeaks.org/">Parks 'n' Peaks</a>, <a href="https://ontheair.nz">ZLOTA</a>, <a href="https://www.wota.org.uk/">WOTA</a>, <a href="https://llota.app">LLOTA</a>, <a href="https://wwtota.com">WWTOTA</a>, <a href="https://tilesontheair.com/">Tiles on the Air</a>, the <a href="https://ukpacketradio.network/">UK Packet Repeater Network</a>, and any site based on the <a href="https://github.com/nischu/xOTA">xOTA software by nischu</a>.</p>
<p>Spothole can retrieve alerts from: <a href="https://www.ng3k.com/">NG3K</a>, <a href="https://pota.app">POTA</a>, <a href="https://www.sota.org.uk/">SOTA</a>, <a href="https://wwff.co/">WWFF</a>, <a href="https://www.parksnpeaks.org/">Parks 'n' Peaks</a>, <a href="https://www.wota.org.uk/">WOTA</a> and <a href="https://www.beachesontheair.com/">BOTA</a>.</p> <p>Spothole can retrieve alerts from: <a href="https://www.ng3k.com/">NG3K</a>, <a href="https://pota.app">POTA</a>, <a href="https://www.sota.org.uk/">SOTA</a>, <a href="https://wwff.co/">WWFF</a>, <a href="https://www.parksnpeaks.org/">Parks 'n' Peaks</a>, <a href="https://www.wota.org.uk/">WOTA</a> and <a href="https://www.beachesontheair.com/">BOTA</a>.</p>
<p>Spothole can retrieve solar and propagation condition data from <a href="https://www.hamqsl.com">HamQSL</a>.</p> <p>Spothole can retrieve solar and propagation condition data from <a href="https://www.hamqsl.com">HamQSL</a>.</p>
<p>Spothole can also perform lookups for callsign data on behalf of the user from <a href="https://qrz.com">QRZ.com</a> and <a href="https://hamqth.com">HamQTH</a>.</p>
<p>Note that the server owner has not necessarily enabled all these data sources. In particular it is common to disable RBN, to avoid the server being swamped with FT8 traffic, and to disable APRS-IS and UK Packet Net so that the server only displays stations where there is likely to be an operator physically present for a QSO.</p> <p>Note that the server owner has not necessarily enabled all these data sources. In particular it is common to disable RBN, to avoid the server being swamped with FT8 traffic, and to disable APRS-IS and UK Packet Net so that the server only displays stations where there is likely to be an operator physically present for a QSO.</p>
<p>Between the various data sources, the following Special Interest Groups (SIGs) are supported: Parks on the Air (POTA), Summits on the Air (SOTA), Worldwide Flora & Fauna (WWFF), Global Mountain Activity (GMA), Worldwide Bunkers on the Air (WWBOTA), HuMPs Excluding Marilyns Award (HEMA), Islands on the Air (IOTA), Mills on the Air (MOTA), the Amateur Radio Lighthouse Socirty (ARLHS), International Lighthouse Lightship Weekend (ILLW), Silos on the Air (SIOTA), World Castles Award (WCA), New Zealand on the Air (ZLOTA), Keith Roget Memorial National Parks Award (KRMNPA), Wainwrights on the Air (WOTA), Beaches on the Air (BOTA), Lagos y Lagunas On the Air (LLOTA), Towers on the Air (WWTOTA), Tiles on the Air, Worked All Britain (WAB), Worked All Ireland (WAI), and Toilets on the Air (TOTA).</p> <p>Between the various data sources, the following Special Interest Groups (SIGs) are supported: Parks on the Air (POTA), Summits on the Air (SOTA), Worldwide Flora & Fauna (WWFF), Global Mountain Activity (GMA), Worldwide Bunkers on the Air (WWBOTA), HuMPs Excluding Marilyns Award (HEMA), Islands on the Air (IOTA), Mills on the Air (MOTA), the Amateur Radio Lighthouse Socirty (ARLHS), International Lighthouse Lightship Weekend (ILLW), Silos on the Air (SIOTA), World Castles Award (WCA), New Zealand on the Air (ZLOTA), Keith Roget Memorial National Parks Award (KRMNPA), Wainwrights on the Air (WOTA), Beaches on the Air (BOTA), Lagos y Lagunas On the Air (LLOTA), Towers on the Air (WWTOTA), Tiles on the Air, Worked All Britain (WAB), Worked All Ireland (WAI), and Toilets on the Air (TOTA).</p>
<p>As of the time of writing in November 2025, I think Spothole captures essentially all outdoor radio programmes that have a defined reference list, and almost certainly those that have a spotting/alerting API. If you know of one I've missed, please let me know!</p> <p>As of the time of writing in November 2025, I think Spothole captures essentially all outdoor radio programmes that have a defined reference list, and almost certainly those that have a spotting/alerting API. If you know of one I've missed, please let me know!</p>
@@ -54,9 +55,10 @@
<h2 class="mt-4">Data Accuracy</h2> <h2 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>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>
<h2 id="privacy" class="mt-4">Privacy</h2> <h2 id="privacy" class="mt-4">Privacy</h2>
<p>Spothole collects no data about you, and there is no way to enter personally identifying information into the site apart from by spotting and alerting through Spothole or the various services it connects to. 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> <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>
<p>Settings you select from Spothole's menus are sent to the server, in order to provide the data with the requested filters. They are also stored in your browser's local storage, so that your preferences are remembered between sessions.</p> <p>Settings you select from Spothole's menus are sent to the server, in order to provide the data with the requested filters. They are also stored in your browser's local storage, so that your preferences are remembered between sessions.</p>
<p>There are no trackers, no ads, and no cookies.</p> <p>The data you provide can optionally include your login credentials for QRZ.com and HamQTH. You can provide these in the "Data" menu of most pages. If you do, Spothole will augment the data it produces with lookups from these services, which can for example provide more accurate markers on the map tab, and operator names when you mouse over a DX callsign. Spothole will still work fine if you don't provide these. The values you enter are sent to Spothole via HTTPS so are protected in transit, though of course you do have to trust Spothole with this sensitive data in order to use this feature.</p>
<p>Spothole uses no trackers, no ads, and no cookies.</p>
{% if len(web_ui_options["support-button-html"]) > 0 %} {% if len(web_ui_options["support-button-html"]) > 0 %}
<p><strong>Caveat: </strong> The owner of this server has chosen to inject their own content into the "spots" page. This is designed for a "donate" or "support this server" button. The functionality of this injected content is the responsibility of the server owner, rather than the Spothole software.</p> <p><strong>Caveat: </strong> The owner of this server has chosen to inject their own content into the "spots" page. This is designed for a "donate" or "support this server" button. The functionality of this injected content is the responsibility of the server owner, rather than the Spothole software.</p>
{% end %} {% end %}
@@ -67,7 +69,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=1778337803"></script> <script src="/js/common.js?v=1778341969"></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 %}

View File

@@ -69,8 +69,8 @@
</div> </div>
<script src="/js/common.js?v=1778337803"></script> <script src="/js/common.js?v=1778341969"></script>
<script src="/js/add-spot.js?v=1778337803"></script> <script src="/js/add-spot.js?v=1778341969"></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 %}

View File

@@ -50,14 +50,28 @@
</div> </div>
</div> </div>
<div id="data-area" class="appearing-panel card mb-3">
{% module Template("widgets/data-area-header.html", web_ui_options=web_ui_options) %}
<div class="card-body">
<div class="row row-cols-1 row-cols-md-4 g-4">
<div class="col">
{% module Template("cards/qrz.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
<div id="table-container"> <div id="table-container">
<table id="table" class="table"><thead><tr></tr></thead><tbody></tbody></table> <table id="table" class="table"><thead><tr></tr></thead><tbody></tbody></table>
</div> </div>
</div> </div>
<script src="/js/common.js?v=1778337803"></script> <script src="/js/common.js?v=1778341969"></script>
<script src="/js/alerts.js?v=1778337803"></script> <script src="/js/alerts.js?v=1778341969"></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 %}

View File

@@ -55,6 +55,20 @@
</div> </div>
</div> </div>
<div id="data-area" class="appearing-panel card mb-3">
{% module Template("widgets/data-area-header.html", web_ui_options=web_ui_options) %}
<div class="card-body">
<div class="row row-cols-1 row-cols-md-4 g-4">
<div class="col">
{% module Template("cards/qrz.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
<div id="bands-container"></div> <div id="bands-container"></div>
</div> </div>
@@ -62,9 +76,9 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/common.js?v=1778337803"></script> <script src="/js/common.js?v=1778341969"></script>
<script src="/js/spotsbandsandmap.js?v=1778337803"></script> <script src="/js/spotsbandsandmap.js?v=1778341969"></script>
<script src="/js/bands.js?v=1778337803"></script> <script src="/js/bands.js?v=1778341969"></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 %}

View File

@@ -24,7 +24,7 @@
<title>Spothole</title> <title>Spothole</title>
<link rel="stylesheet" href="/css/style.css?v=1778337803" type="text/css"> <link rel="stylesheet" href="/css/style.css?v=1778341969" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous"> integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" /> <link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
@@ -46,9 +46,9 @@
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=1778337803"></script> <script src="https://misc.ianrenton.com/jsutils/utils.js?v=1778341969"></script>
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1778337803"></script> <script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1778341969"></script>
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1778337803"></script> <script src="https://misc.ianrenton.com/jsutils/geo.js?v=1778341969"></script>
</head> </head>
<body> <body>

View File

@@ -0,0 +1,27 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">HamQTH</h5>
<div class="card-text spothole-card-text">
<div class="form-check mb-2">
<input type="checkbox" class="storeable-checkbox form-check-input" id="hamqth-enabled" onchange="saveSettings();">
<label for="hamqth-enabled" class="form-check-label">Use data from HamQTH</label>
</div>
<div class="mb-2">
<input type="text" class="storeable-text form-control form-control-sm" id="hamqth-username" placeholder="Username (Callsign)" onchange="saveSettings();" autocomplete="username">
</div>
<div class="mb-2">
<input type="password" class="password-field form-control form-control-sm" id="hamqth-password" placeholder="Password" data-remember-checkbox="hamqth-remember-password" onchange="saveSettings();" autocomplete="current-password">
</div>
<div class="form-check">
<input type="checkbox" class="storeable-checkbox form-check-input" id="hamqth-remember-password" onchange="saveSettings();">
<label for="hamqth-remember-password" class="form-check-label">Remember password</label>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="location.reload();">Reload with this data</button>
</div>
<div class="mt-1">
<small>See <a href="/about#privacy">Privacy</a> for more information.</small>
</div>
</div>
</div>
</div>

27
templates/cards/qrz.html Normal file
View File

@@ -0,0 +1,27 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">QRZ.com</h5>
<div class="card-text spothole-card-text">
<div class="form-check mb-2">
<input type="checkbox" class="storeable-checkbox form-check-input" id="qrz-enabled" onchange="saveSettings();">
<label for="qrz-enabled" class="form-check-label">Use data from QRZ.com</label>
</div>
<div class="mb-2">
<input type="text" class="storeable-text form-control form-control-sm" id="qrz-username" placeholder="Username (Callsign)" onchange="saveSettings();" autocomplete="username">
</div>
<div class="mb-2">
<input type="password" class="password-field form-control form-control-sm" id="qrz-password" placeholder="Password" data-remember-checkbox="qrz-remember-password" onchange="saveSettings();" autocomplete="current-password">
</div>
<div class="form-check">
<input type="checkbox" class="storeable-checkbox form-check-input" id="qrz-remember-password" onchange="saveSettings();">
<label for="qrz-remember-password" class="form-check-label">Remember password</label>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="location.reload();">Reload with this data</button>
</div>
<div class="mt-1">
<small>See <a href="/about#privacy">Privacy</a> for more information.</small>
</div>
</div>
</div>
</div>

View File

@@ -230,8 +230,8 @@
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
<script src="/js/common.js?v=1778337803"></script> <script src="/js/common.js?v=1778341969"></script>
<script src="/js/conditions.js?v=1778337803"></script> <script src="/js/conditions.js?v=1778341969"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active"); $("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>

View File

@@ -59,6 +59,20 @@
</div> </div>
</div> </div>
</div> </div>
<div id="data-area" class="appearing-panel card mb-3">
{% module Template("widgets/data-area-header.html", web_ui_options=web_ui_options) %}
<div class="card-body">
<div class="row row-cols-1 row-cols-md-4 g-4">
<div class="col">
{% module Template("cards/qrz.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
@@ -79,9 +93,9 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/common.js?v=1778337802"></script> <script src="/js/common.js?v=1778341968"></script>
<script src="/js/spotsbandsandmap.js?v=1778337802"></script> <script src="/js/spotsbandsandmap.js?v=1778341968"></script>
<script src="/js/map.js?v=1778337802"></script> <script src="/js/map.js?v=1778341968"></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 %}

View File

@@ -62,12 +62,6 @@
<div class="col"> <div class="col">
{% module Template("cards/number-of-spots.html", web_ui_options=web_ui_options) %} {% module Template("cards/number-of-spots.html", web_ui_options=web_ui_options) %}
</div> </div>
<div class="col">
{% module Template("cards/location.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/worked-calls.html", web_ui_options=web_ui_options) %}
</div>
<div class="col"> <div class="col">
{% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %} {% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %}
</div> </div>
@@ -81,6 +75,26 @@
</div> </div>
</div> </div>
<div id="data-area" class="appearing-panel card mb-3">
{% module Template("widgets/data-area-header.html", web_ui_options=web_ui_options) %}
<div class="card-body">
<div class="row row-cols-1 row-cols-md-4 g-4">
<div class="col">
{% module Template("cards/qrz.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/location.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/worked-calls.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
<div id="table-container"> <div id="table-container">
<table id="table" class="table"><thead><tr></tr></thead><tbody></tbody></table> <table id="table" class="table"><thead><tr></tr></thead><tbody></tbody></table>
</div> </div>
@@ -90,9 +104,9 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/common.js?v=1778337802"></script> <script src="/js/common.js?v=1778341969"></script>
<script src="/js/spotsbandsandmap.js?v=1778337802"></script> <script src="/js/spotsbandsandmap.js?v=1778341969"></script>
<script src="/js/spots.js?v=1778337802"></script> <script src="/js/spots.js?v=1778341969"></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 %}

View File

@@ -59,8 +59,8 @@
</div> </div>
</div> </div>
<script src="/js/common.js?v=1778337803"></script> <script src="/js/common.js?v=1778341969"></script>
<script src="/js/status.js?v=1778337803"></script> <script src="/js/status.js?v=1778341969"></script>
<script> <script>
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --> $(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
</script> </script>

View File

@@ -0,0 +1,10 @@
<div class="card-header">
<div class="row">
<div class="col-auto me-auto">
Data
</div>
<div class="col-auto d-inline-flex">
<button id="close-data-button" type="button" class="btn-close btn-close" aria-label="Close" onclick="closeDataPanel();"></button>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
<div class="d-inline-flex gap-1"> <div class="d-inline-flex gap-1">
<button id="filters-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i>&nbsp;Filters</button> <button id="filters-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i>&nbsp;Filters</button>
<button id="display-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i>&nbsp;Display</button> <button id="display-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i>&nbsp;Display</button>
<button id="data-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleDataPanel();"><i class="fa-solid fa-database"></i>&nbsp;Data</button>
</div> </div>

View File

@@ -33,6 +33,7 @@ function buildQueryString() {
if ($("#dxpeditions_skip_max_duration_check")[0].checked) { if ($("#dxpeditions_skip_max_duration_check")[0].checked) {
str = str + "&dxpeditions_skip_max_duration_check=true"; str = str + "&dxpeditions_skip_max_duration_check=true";
} }
str = str + getCredentialQueryString();
return str; return str;
} }

View File

@@ -34,6 +34,7 @@ function buildQueryString() {
str = str + "max_age=" + $("#max-spot-age option:selected").val(); str = str + "max_age=" + $("#max-spot-age option:selected").val();
// Additional filters for the bands view: No dupes, no QRT // Additional filters for the bands view: No dupes, no QRT
str = str + "&dedupe=true&allow_qrt=false"; str = str + "&dedupe=true&allow_qrt=false";
str = str + getCredentialQueryString();
return str; return str;
} }

View File

@@ -10,15 +10,25 @@ function saveSettings() {
if (useLocalStorage) { if (useLocalStorage) {
// Find all storeable UI elements, store a key of "element id:property name" mapped to the value of that // Find all storeable UI elements, store a key of "element id:property name" mapped to the value of that
// property. For a checkbox, that's the "checked" property. // property. For a checkbox, that's the "checked" property.
$(".storeable-checkbox").each(function() { $(".storeable-checkbox").each(function () {
localStorage.setItem("#" + $(this)[0].id + ":checked", JSON.stringify($(this)[0].checked)); localStorage.setItem("#" + $(this)[0].id + ":checked", JSON.stringify($(this)[0].checked));
}); });
$(".storeable-select").each(function() { $(".storeable-select").each(function () {
localStorage.setItem("#" + $(this)[0].id + ":value", JSON.stringify($(this)[0].value)); localStorage.setItem("#" + $(this)[0].id + ":value", JSON.stringify($(this)[0].value));
}); });
$(".storeable-text").each(function() { $(".storeable-text").each(function () {
localStorage.setItem("#" + $(this)[0].id + ":value", JSON.stringify($(this)[0].value)); localStorage.setItem("#" + $(this)[0].id + ":value", JSON.stringify($(this)[0].value));
}); });
// Password fields are only saved if the corresponding "remember password" checkbox is ticked.
$(".password-field").each(function () {
var pwKey = "#" + $(this)[0].id + ":value";
var rememberCheckboxId = $(this).data("remember-checkbox");
if (rememberCheckboxId && $("#" + rememberCheckboxId)[0] && $("#" + rememberCheckboxId)[0].checked) {
localStorage.setItem(pwKey, JSON.stringify($(this)[0].value));
} else {
localStorage.removeItem(pwKey);
}
});
} }
} }
@@ -26,7 +36,7 @@ function saveSettings() {
function loadSettings() { function loadSettings() {
if (useLocalStorage) { if (useLocalStorage) {
// Find all local storage entries and push their data to the corresponding UI element // Find all local storage entries and push their data to the corresponding UI element
Object.keys(localStorage).forEach(function(key) { Object.keys(localStorage).forEach(function (key) {
if (key.startsWith("#") && key.includes(":")) { if (key.startsWith("#") && key.includes(":")) {
// Split the key back into an element ID and a property // Split the key back into an element ID and a property
var split = key.split(":"); var split = key.split(":");
@@ -108,9 +118,9 @@ function getQueryStringFor(parameter) {
// For a parameter, such as dx_continent, get the filter options that are currently selected in the UI. // For a parameter, such as dx_continent, get the filter options that are currently selected in the UI.
function getSelectedFilterOptions(parameter) { function getSelectedFilterOptions(parameter) {
return $(".filter-button-" + parameter).filter(function() { return $(".filter-button-" + parameter).filter(function () {
return this.checked; return this.checked;
}).map(function() { }).map(function () {
return this.value; return this.value;
}).get().join(","); }).get().join(",");
} }
@@ -118,7 +128,7 @@ function getSelectedFilterOptions(parameter) {
// For a parameter, such as dx_continent, return true if all possible options are enabled. (In this case, we don't need // For a parameter, such as dx_continent, return true if all possible options are enabled. (In this case, we don't need
// to bother sending this as one of the query parameters to the API; no parameter provided implies "send everything".) // to bother sending this as one of the query parameters to the API; no parameter provided implies "send everything".)
function allFilterOptionsSelected(parameter) { function allFilterOptionsSelected(parameter) {
var filter = $(".filter-button-" + parameter).filter(function() { var filter = $(".filter-button-" + parameter).filter(function () {
return !this.checked; return !this.checked;
}).get(); }).get();
return filter.length == 0; return filter.length == 0;
@@ -137,7 +147,7 @@ function generateMultiToggleFilterCard(elementID, filterQuery, options) {
// Method called when "All" or "None" is clicked // Method called when "All" or "None" is clicked
function toggleFilterButtons(filterQuery, state) { function toggleFilterButtons(filterQuery, state) {
$(".filter-button-" + filterQuery).each(function() { $(".filter-button-" + filterQuery).each(function () {
$(this).prop('checked', state); $(this).prop('checked', state);
}); });
filtersUpdated(); filtersUpdated();
@@ -218,8 +228,9 @@ function listenForOSThemeChange() {
// Panel toggle functions // Panel toggle functions
const PANELS = [ const PANELS = [
{ area: "#filters-area",button: "#filters-button" }, {area: "#filters-area", button: "#filters-button"},
{ area: "#display-area", button: "#display-button" }, {area: "#display-area", button: "#display-button"},
{area: "#data-area", button: "#data-button"},
]; ];
// Toggle a panel open or closed. If opening, all other visible panels are closed first. // Toggle a panel open or closed. If opening, all other visible panels are closed first.
@@ -239,18 +250,58 @@ function togglePanel(areaId) {
// Close a panel and deactivate its toggle button. // Close a panel and deactivate its toggle button.
function closePanel(areaId) { function closePanel(areaId) {
const panel = PANELS.find(p => p.area === areaId); const panel = PANELS.find(p => p.area === areaId);
if (panel) { $(panel.button).button("toggle"); } if (panel) {
$(panel.button).button("toggle");
}
$(areaId).hide(); $(areaId).hide();
} }
function toggleFiltersPanel() { togglePanel("#filters-area"); } function toggleFiltersPanel() {
function closeFiltersPanel() { closePanel("#filters-area"); } togglePanel("#filters-area");
function toggleDisplayPanel() { togglePanel("#display-area"); } }
function closeDisplayPanel() { closePanel("#display-area"); }
function closeFiltersPanel() {
closePanel("#filters-area");
}
function toggleDisplayPanel() {
togglePanel("#display-area");
}
function closeDisplayPanel() {
closePanel("#display-area");
}
function toggleDataPanel() {
togglePanel("#data-area");
}
function closeDataPanel() {
closePanel("#data-area");
}
// Build a query string fragment containing any QRZ.com / HamQTH credentials the user has supplied,
// provided the corresponding "enabled" checkbox is ticked.
function getCredentialQueryString() {
var str = "";
if ($("#qrz-enabled")[0] && $("#qrz-enabled")[0].checked) {
var qrzUsername = $("#qrz-username").val();
var qrzPassword = $("#qrz-password").val();
if (qrzUsername) str += "&qrz_username=" + encodeURIComponent(qrzUsername);
if (qrzPassword) str += "&qrz_password=" + encodeURIComponent(qrzPassword);
}
if ($("#hamqth-enabled")[0] && $("#hamqth-enabled")[0].checked) {
var hamqthUsername = $("#hamqth-username").val();
var hamqthPassword = $("#hamqth-password").val();
if (hamqthUsername) str += "&hamqth_username=" + encodeURIComponent(hamqthUsername);
if (hamqthPassword) str += "&hamqth_password=" + encodeURIComponent(hamqthPassword);
}
return str;
}
// Startup // Startup
$(document).ready(function() { $(document).ready(function () {
usePreferredTheme(); usePreferredTheme();
listenForOSThemeChange(); listenForOSThemeChange();
}); });

View File

@@ -49,6 +49,7 @@ function buildQueryString() {
str = str + "max_age=" + $("#max-spot-age option:selected").val(); str = str + "max_age=" + $("#max-spot-age option:selected").val();
// Additional filters for the map view: No dupes, no QRT, only spots with good locations // Additional filters for the map view: No dupes, no QRT, only spots with good locations
str = str + "&dedupe=true&allow_qrt=false&needs_good_location=true"; str = str + "&dedupe=true&allow_qrt=false&needs_good_location=true";
str = str + getCredentialQueryString();
return str; return str;
} }

View File

@@ -89,6 +89,7 @@ function buildQueryString() {
if ($("#search").val() != "") { if ($("#search").val() != "") {
str = str + "&text_includes=" + encodeURIComponent($("#search").val()); str = str + "&text_includes=" + encodeURIComponent($("#search").val());
} }
str = str + getCredentialQueryString();
return str; return str;
} }