Provide UTC/local toggle. Closes #32

This commit is contained in:
Ian Renton
2025-10-08 17:16:56 +01:00
parent 62c187178b
commit 1843286f92
6 changed files with 109 additions and 69 deletions

View File

@@ -138,35 +138,35 @@ class WebServer:
match k: match k:
case "since": case "since":
since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC) since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC)
spots = [s for s in spots if s.time > since] spots = [s for s in spots if s.time and s.time > since]
case "max_age": case "max_age":
max_age = int(query.get(k)) max_age = int(query.get(k))
since = datetime.now(pytz.UTC) - timedelta(seconds=max_age) since = datetime.now(pytz.UTC) - timedelta(seconds=max_age)
spots = [s for s in spots if s.time > since] spots = [s for s in spots if s.time and s.time > since]
case "received_since": case "received_since":
since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC) since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC)
spots = [s for s in spots if s.received_time > since] spots = [s for s in spots if s.received_time and s.received_time > since]
case "source": case "source":
sources = query.get(k).split(",") sources = query.get(k).split(",")
spots = [s for s in spots if s.source in sources] spots = [s for s in spots if s.source and s.source in sources]
case "sig": case "sig":
sigs = query.get(k).split(",") sigs = query.get(k).split(",")
spots = [s for s in spots if s.sig in sigs] spots = [s for s in spots if s.sig and s.sig in sigs]
case "band": case "band":
bands = query.get(k).split(",") bands = query.get(k).split(",")
spots = [s for s in spots if s.band in bands] spots = [s for s in spots if s.band and s.band in bands]
case "mode": case "mode":
modes = query.get(k).split(",") modes = query.get(k).split(",")
spots = [s for s in spots if s.mode in modes] spots = [s for s in spots if s.mode in modes]
case "mode_type": case "mode_type":
mode_families = query.get(k).split(",") mode_families = query.get(k).split(",")
spots = [s for s in spots if s.mode_type in mode_families] spots = [s for s in spots if s.mode_type and s.mode_type in mode_families]
case "dx_continent": case "dx_continent":
dxconts = query.get(k).split(",") dxconts = query.get(k).split(",")
spots = [s for s in spots if s.dx_continent in dxconts] spots = [s for s in spots if s.dx_continent and s.dx_continent in dxconts]
case "de_continent": case "de_continent":
deconts = query.get(k).split(",") deconts = query.get(k).split(",")
spots = [s for s in spots if s.de_continent in deconts] spots = [s for s in spots if s.de_continent and s.de_continent in deconts]
# If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys. # If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys.
if "limit" in query.keys(): if "limit" in query.keys():
spots = spots[:int(query.get("limit"))] spots = spots[:int(query.get("limit"))]
@@ -195,7 +195,7 @@ class WebServer:
match k: match k:
case "received_since": case "received_since":
since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC) since = datetime.fromtimestamp(int(query.get(k)), pytz.UTC)
alerts = [a for a in alerts if a.received_time > since] alerts = [a for a in alerts if a.received_time and a.received_time > since]
case "max_duration": case "max_duration":
max_duration = int(query.get(k)) max_duration = int(query.get(k))
# Check the duration if end_time is provided. If end_time is not provided, assume the activation is # Check the duration if end_time is provided. If end_time is not provided, assume the activation is
@@ -204,13 +204,13 @@ class WebServer:
not a.end_time] not a.end_time]
case "source": case "source":
sources = query.get(k).split(",") sources = query.get(k).split(",")
alerts = [a for a in alerts if a.source in sources] alerts = [a for a in alerts if a.source and a.source in sources]
case "sig": case "sig":
sigs = query.get(k).split(",") sigs = query.get(k).split(",")
alerts = [a for a in alerts if a.sig in sigs] alerts = [a for a in alerts if a.sig and a.sig in sigs]
case "dx_continent": case "dx_continent":
dxconts = query.get(k).split(",") dxconts = query.get(k).split(",")
alerts = [a for a in alerts if a.dx_continent in dxconts] alerts = [a for a in alerts if a.dx_continent and a.dx_continent in dxconts]
# If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys. # If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys.
if "limit" in query.keys(): if "limit" in query.keys():
alerts = alerts[:int(query.get("limit"))] alerts = alerts[:int(query.get("limit"))]

View File

@@ -7,38 +7,44 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<p class="d-inline-flex gap-1"> <p class="d-inline-flex gap-1">
<button id="settings-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button">Settings</button> <button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button">Filters</button>
</p> </p>
</div> </div>
</div> </div>
<div id="settings-area" class="appearing-panel card mb-3"> <div id="filters-area" class="appearing-panel card mb-3">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
<div class="row"> <div class="row">
<div class="col-auto me-auto"> <div class="col-auto me-auto">
Settings filters
</div> </div>
<div class="col-auto d-inline-flex"> <div class="col-auto d-inline-flex">
<button id="close-settings-button" type="button" class="btn-close btn-close-white" aria-label="Close"></button> <button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close"></button>
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="settings-container" class="row row-cols-1 row-cols-md-3 g-4"></div> <div id="filters-container" class="row row-cols-1 row-cols-md-3 g-4"></div>
</div> </div>
</div> </div>
<div id="table-container"></div> <div id="table-container"></div>
<p>Alerts to view: <p>
<select id="alerts-to-fetch" class="storeable-select form-select ms-2" onclick="filtersUpdated();" style="width: 5em;display: inline-block;"> <span class="form-check form-switch d-inline-block">
<option value="25">25</option> <input class="form-check-input storeable-checkbox" type="checkbox" role="switch" id="useLocalTime" onclick="localTimeUpdated();">
<option value="50">50</option> <label class="form-check-label" for="useLocalTime">Local time</label>
<option value="100" selected>100</option> </span>
<option value="200">200</option> <span class="ms-5">Alerts to view:
<option value="500">500</option> <select id="alerts-to-fetch" class="storeable-select form-select ms-2" onclick="filtersUpdated();" style="width: 5em;display: inline-block;">
</select> <option value="25">25</option>
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="200">200</option>
<option value="500">500</option>
</select>
</span>
</p> </p>
</div> </div>

View File

@@ -22,38 +22,44 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<p class="d-inline-flex gap-1"> <p class="d-inline-flex gap-1">
<button id="settings-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button">Settings</button> <button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button">Filters</button>
</p> </p>
</div> </div>
</div> </div>
<div id="settings-area" class="appearing-panel card mb-3"> <div id="filters-area" class="appearing-panel card mb-3">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
<div class="row"> <div class="row">
<div class="col-auto me-auto"> <div class="col-auto me-auto">
Settings filters
</div> </div>
<div class="col-auto d-inline-flex"> <div class="col-auto d-inline-flex">
<button id="close-settings-button" type="button" class="btn-close btn-close-white" aria-label="Close"></button> <button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close"></button>
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="settings-container-1" class="row row-cols-1 g-4 mb-4"></div> <div id="filters-container-1" class="row row-cols-1 g-4 mb-4"></div>
<div id="settings-container-2" class="row row-cols-1 row-cols-md-4 g-4"></div> <div id="filters-container-2" class="row row-cols-1 row-cols-md-4 g-4"></div>
</div> </div>
</div> </div>
<div id="table-container"></div> <div id="table-container"></div>
<p>Spots to view: <p>
<select id="spots-to-fetch" class="storeable-select form-select ms-2" onclick="filtersUpdated();" style="width: 5em;display: inline-block;"> <span class="form-check form-switch d-inline-block">
<option value="10">10</option> <input class="form-check-input storeable-checkbox" type="checkbox" role="switch" id="useLocalTime" onclick="localTimeUpdated();">
<option value="25">25</option> <label class="form-check-label" for="useLocalTime">Local time</label>
<option value="50" selected>50</option> </span>
<option value="100">100</option> <span class="ms-5">Spots to view:
</select> <select id="spots-to-fetch" class="storeable-select form-select ms-2 d-inline-block" onclick="filtersUpdated();" style="width: 5em;">
<option value="10">10</option>
<option value="25">25</option>
<option value="50" selected>50</option>
<option value="100">100</option>
</select>
</span>
</p> </p>
</div> </div>

View File

@@ -35,10 +35,13 @@ function buildQueryString() {
// Update the alerts table // Update the alerts table
function updateTable() { function updateTable() {
// Use local time instead of UTC?
var useLocalTime = $("#useLocalTime")[0].checked;
// Populate table with headers // Populate table with headers
let table = $('<table class="table table-striped-custom table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>'); let table = $('<table class="table table-striped-custom table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
table.find('thead tr').append(`<th>Start UTC</th>`); table.find('thead tr').append(`<th>${useLocalTime ? "Start&nbsp;(Local)" : "Start&nbsp;UTC"}</th>`);
table.find('thead tr').append(`<th>End UTC</th>`); table.find('thead tr').append(`<th>${useLocalTime ? "End&nbsp;(Local)" : "End&nbsp;UTC"}</th>`);
table.find('thead tr').append(`<th>DX</th>`); table.find('thead tr').append(`<th>DX</th>`);
table.find('thead tr').append(`<th class='hideonmobile'>Freq<span class='hideonmobile'>uencie</span>s & Modes</th>`); table.find('thead tr').append(`<th class='hideonmobile'>Freq<span class='hideonmobile'>uencie</span>s & Modes</th>`);
table.find('thead tr').append(`<th class='hideonmobile'>Comment</th>`); table.find('thead tr').append(`<th class='hideonmobile'>Comment</th>`);
@@ -82,18 +85,31 @@ function addAlertRowsToTable(tbody, alerts) {
// Create row // Create row
let $tr = $('<tr>'); let $tr = $('<tr>');
// Format UTC times for display. Start time is displayed as e.g. 7 Oct 12:34 unless the time is in a different // Use local time instead of UTC?
// year to the current year, in which case the year is inserted between month and hour. var useLocalTime = $("#useLocalTime")[0].checked;
// Get times for the alert, and convert to local time if necessary.
var start_time = moment.unix(a["start_time"]).utc();
if (useLocalTime) {
start_time = start_time.local();
}
var end_time_unix = moment.unix(a["end_time"]);
var end_time = end_time_unix.utc();
if (useLocalTime) {
end_time = end_time.local();
}
// Format the times for display. Start time is displayed as e.g. 7 Oct 12:34 unless the time is in a
// different year to the current year, in which case the year is inserted between month and hour.
// If the time is set to local not UTC, and the date in local time is "today", we display that instead.
// End time is displayed the same as above, except if the end date is the same as the start date, in which case // End time is displayed the same as above, except if the end date is the same as the start date, in which case
// just e.g. 23:45 is used. Finally, if there is no end date set, "---" is displayed. // just e.g. 23:45 is used. Finally, if there is no end date set, "---" is displayed.
var start_time = moment.unix(a["start_time"]).utc();
var start_time_formatted = start_time.format("D MMM HH:mm"); var start_time_formatted = start_time.format("D MMM HH:mm");
if (start_time.format("YYYY") != moment().format("YYYY")) { if (start_time.format("YYYY") != moment().format("YYYY")) {
start_time_formatted = start_time.format("D MMM YYYY HH:mm"); start_time_formatted = start_time.format("D MMM YYYY HH:mm");
} else if (useLocalTime && start_time.format("D MMM YYYY") == moment().format("D MMM YYYY")) {
start_time_formatted = start_time.format("[Today] HH:mm");
} }
var end_time_unix = moment.unix(a["end_time"]);
var end_time = end_time_unix.utc();
var end_time_formatted = "---"; var end_time_formatted = "---";
if (end_time_unix != null && end_time_unix > 0 && end_time != null) { if (end_time_unix != null && end_time_unix > 0 && end_time != null) {
var end_time_formatted = end_time.format("HH:mm"); var end_time_formatted = end_time.format("HH:mm");
@@ -166,14 +182,14 @@ function loadOptions() {
options = jsonData; options = jsonData;
// Populate the filters panel // Populate the filters panel
$("#settings-container").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"])); $("#filters-container").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"]));
$("#settings-container").append(generateMultiToggleFilterCard("Sources", "source", options["alert_sources"])); $("#filters-container").append(generateMultiToggleFilterCard("Sources", "source", options["alert_sources"]));
// Options doesn't give us anything for Max Duration as it's a free numeric input, but we generate our own // Options doesn't give us anything for Max Duration as it's a free numeric input, but we generate our own
// filter card for this. // filter card for this.
$("#settings-container").append(generateMaxDurationDropdownFilterCard(options["alert_sources"])); $("#filters-container").append(generateMaxDurationDropdownFilterCard(options["alert_sources"]));
// Load settings from settings storage // Load filters from settings storage
loadSettings(); loadSettings();
// Load alerts and set up the timer // Load alerts and set up the timer
@@ -213,12 +229,12 @@ function filtersUpdated() {
} }
// Set up UI element event listeners, after the document is ready // Set up UI element event listeners, after the document is ready
function setUpEventListeners() { function setUpEventListeners() {
$("#settings-button").click(function() { $("#filters-button").click(function() {
$("#settings-area").toggle(); $("#filters-area").toggle();
}); });
$("#close-settings-button").click(function() { $("#close-filters-button").click(function() {
$("#settings-button").button("toggle"); $("#filters-button").button("toggle");
$("#settings-area").hide(); $("#filters-area").hide();
}); });
} }

View File

@@ -97,6 +97,12 @@ function escapeHtml(str) {
return str.replace(/[&<>"'`]/g, escapeCharacter); return str.replace(/[&<>"'`]/g, escapeCharacter);
} }
// When the "use local time" field is changed, reload the table and save settings
function localTimeUpdated() {
updateTable();
saveSettings();
}
// Save settings to local storage // Save settings to local storage
function saveSettings() { function saveSettings() {
// 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

View File

@@ -31,9 +31,12 @@ function buildQueryString() {
// Update the spots table // Update the spots table
function updateTable() { function updateTable() {
// Use local time instead of UTC?
var useLocalTime = $("#useLocalTime")[0].checked;
// Populate table with headers // Populate table with headers
let table = $('<table class="table table-striped-custom table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>'); let table = $('<table class="table table-striped-custom table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
table.find('thead tr').append(`<th>UTC</th>`); table.find('thead tr').append(`<th>${useLocalTime ? "Local" : "UTC"}</th>`);
table.find('thead tr').append(`<th>DX</th>`); table.find('thead tr').append(`<th>DX</th>`);
table.find('thead tr').append(`<th>Freq<span class='hideonmobile'>uency</span></th>`); table.find('thead tr').append(`<th>Freq<span class='hideonmobile'>uency</span></th>`);
table.find('thead tr').append(`<th>Mode</th>`); table.find('thead tr').append(`<th>Mode</th>`);
@@ -55,8 +58,11 @@ function updateTable() {
$tr.addClass("table-faded"); $tr.addClass("table-faded");
} }
// Format a UTC time for display // Format a UTC or local time for display
var time = moment.unix(s["time"]).utc(); var time = moment.unix(s["time"]).utc();
if (useLocalTime) {
time = time.local();
}
var time_formatted = time.format("HH:mm"); var time_formatted = time.format("HH:mm");
// Format DX flag // Format DX flag
@@ -157,13 +163,13 @@ function loadOptions() {
addBandColourCSS(options["bands"]); addBandColourCSS(options["bands"]);
// Populate the filters panel // Populate the filters panel
$("#settings-container-1").append(generateBandsMultiToggleFilterCard(options["bands"])); $("#filters-container-1").append(generateBandsMultiToggleFilterCard(options["bands"]));
$("#settings-container-2").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"])); $("#filters-container-2").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"]));
$("#settings-container-2").append(generateMultiToggleFilterCard("DE Continent", "de_continent", options["continents"])); $("#filters-container-2").append(generateMultiToggleFilterCard("DE Continent", "de_continent", options["continents"]));
$("#settings-container-2").append(generateMultiToggleFilterCard("Modes", "mode_type", options["mode_types"])); $("#filters-container-2").append(generateMultiToggleFilterCard("Modes", "mode_type", options["mode_types"]));
$("#settings-container-2").append(generateMultiToggleFilterCard("Sources", "source", options["spot_sources"])); $("#filters-container-2").append(generateMultiToggleFilterCard("Sources", "source", options["spot_sources"]));
// Load settings from settings storage // Load filters from settings storage
loadSettings(); loadSettings();
// Load spots and set up the timer // Load spots and set up the timer
@@ -217,12 +223,12 @@ function filtersUpdated() {
// Set up UI element event listeners, after the document is ready // Set up UI element event listeners, after the document is ready
function setUpEventListeners() { function setUpEventListeners() {
$("#settings-button").click(function() { $("#filters-button").click(function() {
$("#settings-area").toggle(); $("#filters-area").toggle();
}); });
$("#close-settings-button").click(function() { $("#close-filters-button").click(function() {
$("#settings-button").button("toggle"); $("#filters-button").button("toggle");
$("#settings-area").hide(); $("#filters-area").hide();
}); });
} }