From 1843286f925389a386d658ed35f85b73c865a3af Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Wed, 8 Oct 2025 17:16:56 +0100 Subject: [PATCH] Provide UTC/local toggle. Closes #32 --- server/webserver.py | 26 ++++++++++----------- views/webpage_alerts.tpl | 32 ++++++++++++++----------- views/webpage_spots.tpl | 32 ++++++++++++++----------- webassets/js/alerts.js | 50 ++++++++++++++++++++++++++-------------- webassets/js/common.js | 6 +++++ webassets/js/spots.js | 32 ++++++++++++++----------- 6 files changed, 109 insertions(+), 69 deletions(-) diff --git a/server/webserver.py b/server/webserver.py index cd9e347..596fd81 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -138,35 +138,35 @@ class WebServer: match k: case "since": 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": max_age = int(query.get(k)) 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": 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": 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": 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": 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": modes = query.get(k).split(",") spots = [s for s in spots if s.mode in modes] case "mode_type": 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": 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": 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 "limit" in query.keys(): spots = spots[:int(query.get("limit"))] @@ -195,7 +195,7 @@ class WebServer: match k: case "received_since": 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": max_duration = int(query.get(k)) # 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] case "source": 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": 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": 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 "limit" in query.keys(): alerts = alerts[:int(query.get("limit"))] diff --git a/views/webpage_alerts.tpl b/views/webpage_alerts.tpl index 33b9227..fc5b3ea 100644 --- a/views/webpage_alerts.tpl +++ b/views/webpage_alerts.tpl @@ -7,38 +7,44 @@

- +

-
+
- Settings + filters
- +
-
+
-

Alerts to view: - +

+ + + + + Alerts to view: + +

diff --git a/views/webpage_spots.tpl b/views/webpage_spots.tpl index e515b37..8ee7450 100644 --- a/views/webpage_spots.tpl +++ b/views/webpage_spots.tpl @@ -22,38 +22,44 @@

- +

-
+
- Settings + filters
- +
-
-
+
+
-

Spots to view: - +

+ + + + + Spots to view: + +

diff --git a/webassets/js/alerts.js b/webassets/js/alerts.js index 1cf3365..87e99a2 100644 --- a/webassets/js/alerts.js +++ b/webassets/js/alerts.js @@ -35,10 +35,13 @@ function buildQueryString() { // Update the alerts table function updateTable() { + // Use local time instead of UTC? + var useLocalTime = $("#useLocalTime")[0].checked; + // Populate table with headers let table = $('').append(''); - table.find('thead tr').append(``); - table.find('thead tr').append(``); + table.find('thead tr').append(``); + table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); @@ -82,18 +85,31 @@ function addAlertRowsToTable(tbody, alerts) { // Create row let $tr = $(''); - // Format UTC 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. + // Use local time instead of UTC? + 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 // 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"); if (start_time.format("YYYY") != moment().format("YYYY")) { 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 = "---"; if (end_time_unix != null && end_time_unix > 0 && end_time != null) { var end_time_formatted = end_time.format("HH:mm"); @@ -166,14 +182,14 @@ function loadOptions() { options = jsonData; // Populate the filters panel - $("#settings-container").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"])); - $("#settings-container").append(generateMultiToggleFilterCard("Sources", "source", options["alert_sources"])); + $("#filters-container").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"])); + $("#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 // 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(); // Load alerts and set up the timer @@ -213,12 +229,12 @@ function filtersUpdated() { } // Set up UI element event listeners, after the document is ready function setUpEventListeners() { - $("#settings-button").click(function() { - $("#settings-area").toggle(); + $("#filters-button").click(function() { + $("#filters-area").toggle(); }); - $("#close-settings-button").click(function() { - $("#settings-button").button("toggle"); - $("#settings-area").hide(); + $("#close-filters-button").click(function() { + $("#filters-button").button("toggle"); + $("#filters-area").hide(); }); } diff --git a/webassets/js/common.js b/webassets/js/common.js index d51115c..4b20b0b 100644 --- a/webassets/js/common.js +++ b/webassets/js/common.js @@ -97,6 +97,12 @@ function escapeHtml(str) { 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 function saveSettings() { // Find all storeable UI elements, store a key of "element id:property name" mapped to the value of that diff --git a/webassets/js/spots.js b/webassets/js/spots.js index 93c7725..92489c2 100644 --- a/webassets/js/spots.js +++ b/webassets/js/spots.js @@ -31,9 +31,12 @@ function buildQueryString() { // Update the spots table function updateTable() { + // Use local time instead of UTC? + var useLocalTime = $("#useLocalTime")[0].checked; + // Populate table with headers let table = $('
Start UTCEnd UTC${useLocalTime ? "Start (Local)" : "Start UTC"}${useLocalTime ? "End (Local)" : "End UTC"}DXFrequencies & ModesComment
').append(''); - table.find('thead tr').append(``); + table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); table.find('thead tr').append(``); @@ -55,8 +58,11 @@ function updateTable() { $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(); + if (useLocalTime) { + time = time.local(); + } var time_formatted = time.format("HH:mm"); // Format DX flag @@ -157,13 +163,13 @@ function loadOptions() { addBandColourCSS(options["bands"]); // Populate the filters panel - $("#settings-container-1").append(generateBandsMultiToggleFilterCard(options["bands"])); - $("#settings-container-2").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"])); - $("#settings-container-2").append(generateMultiToggleFilterCard("DE Continent", "de_continent", options["continents"])); - $("#settings-container-2").append(generateMultiToggleFilterCard("Modes", "mode_type", options["mode_types"])); - $("#settings-container-2").append(generateMultiToggleFilterCard("Sources", "source", options["spot_sources"])); + $("#filters-container-1").append(generateBandsMultiToggleFilterCard(options["bands"])); + $("#filters-container-2").append(generateMultiToggleFilterCard("DX Continent", "dx_continent", options["continents"])); + $("#filters-container-2").append(generateMultiToggleFilterCard("DE Continent", "de_continent", options["continents"])); + $("#filters-container-2").append(generateMultiToggleFilterCard("Modes", "mode_type", options["mode_types"])); + $("#filters-container-2").append(generateMultiToggleFilterCard("Sources", "source", options["spot_sources"])); - // Load settings from settings storage + // Load filters from settings storage loadSettings(); // Load spots and set up the timer @@ -217,12 +223,12 @@ function filtersUpdated() { // Set up UI element event listeners, after the document is ready function setUpEventListeners() { - $("#settings-button").click(function() { - $("#settings-area").toggle(); + $("#filters-button").click(function() { + $("#filters-area").toggle(); }); - $("#close-settings-button").click(function() { - $("#settings-button").button("toggle"); - $("#settings-area").hide(); + $("#close-filters-button").click(function() { + $("#filters-button").button("toggle"); + $("#filters-area").hide(); }); }
UTC${useLocalTime ? "Local" : "UTC"}DXFrequencyMode