diff --git a/core/constants.py b/core/constants.py index d38c4ea..843d772 100644 --- a/core/constants.py +++ b/core/constants.py @@ -19,22 +19,22 @@ MODE_TYPES = ["CW", "PHONE", "DATA"] # Band definitions BANDS = [ - Band(name="160m", start_freq=1800, end_freq=2000, color="#7cfc00", contrast_color="black"), - Band(name="80m", start_freq=3500, end_freq=4000, color="#e550e5", contrast_color="black"), - Band(name="60m", start_freq=5250, end_freq=5410, color="#00008b", contrast_color="white"), - Band(name="40m", start_freq=7000, end_freq=7300, color="#5959ff", contrast_color="white"), - Band(name="30m", start_freq=10100, end_freq=10150, color="#62d962", contrast_color="black"), - Band(name="20m", start_freq=14000, end_freq=14350, color="#f2c40c", contrast_color="black"), - Band(name="17m", start_freq=18068, end_freq=18168, color="#f2f261", contrast_color="black"), - Band(name="15m", start_freq=21000, end_freq=21450, color="#cca166", contrast_color="black"), - Band(name="12m", start_freq=24890, end_freq=24990, color="#b22222", contrast_color="white"), - Band(name="10m", start_freq=28000, end_freq=29700, color="#ff69b4", contrast_color="black"), - Band(name="6m", start_freq=50000, end_freq=54000, color="#FF0000", contrast_color="white"), - Band(name="4m", start_freq=70000, end_freq=70500, color="#cc0044", contrast_color="white"), - Band(name="2m", start_freq=144000, end_freq=148000, color="#FF1493", contrast_color="black"), - Band(name="70cm", start_freq=420000, end_freq=450000, color="#999900", contrast_color="white"), - Band(name="23cm", start_freq=1240000, end_freq=1325000, color="#5AB8C7", contrast_color="black"), - Band(name="13cm", start_freq=2300000, end_freq=2450000, color="#FF7F50", contrast_color="black")] + Band(name="160m", start_freq=1800000, end_freq=2000000, color="#7cfc00", contrast_color="black"), + Band(name="80m", start_freq=3500000, end_freq=4000000, color="#e550e5", contrast_color="black"), + Band(name="60m", start_freq=5250000, end_freq=5410000, color="#00008b", contrast_color="white"), + Band(name="40m", start_freq=7000000, end_freq=7300000, color="#5959ff", contrast_color="white"), + Band(name="30m", start_freq=10100000, end_freq=10150000, color="#62d962", contrast_color="black"), + Band(name="20m", start_freq=14000000, end_freq=14350000, color="#f2c40c", contrast_color="black"), + Band(name="17m", start_freq=18068000, end_freq=18168000, color="#f2f261", contrast_color="black"), + Band(name="15m", start_freq=21000000, end_freq=21450000, color="#cca166", contrast_color="black"), + Band(name="12m", start_freq=24890000, end_freq=24990000, color="#b22222", contrast_color="white"), + Band(name="10m", start_freq=28000000, end_freq=29700000, color="#ff69b4", contrast_color="black"), + Band(name="6m", start_freq=50000000, end_freq=54000000, color="#FF0000", contrast_color="white"), + Band(name="4m", start_freq=70000000, end_freq=70500000, color="#cc0044", contrast_color="white"), + Band(name="2m", start_freq=144000000, end_freq=148000000, color="#FF1493", contrast_color="black"), + Band(name="70cm", start_freq=420000000, end_freq=450000000, color="#999900", contrast_color="white"), + Band(name="23cm", start_freq=1240000000, end_freq=1325000000, color="#5AB8C7", contrast_color="black"), + Band(name="13cm", start_freq=2300000000, end_freq=2450000000, color="#FF7F50", contrast_color="black")] UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0, color="black", contrast_color="white") # Continents diff --git a/core/utils.py b/core/utils.py index 033333e..a047e96 100644 --- a/core/utils.py +++ b/core/utils.py @@ -38,7 +38,7 @@ def infer_mode_type_from_mode(mode): logging.warn("Found an unrecognised mode: " + mode + ". Developer should categorise this.") return None -# Infer a band from a frequency in kHz +# Infer a band from a frequency in Hz def infer_band_from_freq(freq): for b in BANDS: if b.start_freq <= freq <= b.end_freq: @@ -126,10 +126,13 @@ def infer_grid_from_callsign_qrz(call): # Infer a latitude and longitude from a callsign (using DXCC, probably very inaccurate) def infer_latlon_from_callsign_dxcc(call): - data = CALL_INFO_BASIC.get_lat_long(call) - if data and "latitude" in data and "longitude" in data: - return [data["latitude"], data["longitude"]] - else: + try: + data = CALL_INFO_BASIC.get_lat_long(call) + if data and "latitude" in data and "longitude" in data: + return [data["latitude"], data["longitude"]] + else: + return None + except KeyError: return None # Infer a grid locator from a callsign (using DXCC, probably very inaccurate) @@ -138,9 +141,12 @@ def infer_grid_from_callsign_dxcc(call): return latlong_to_locator(latlon[0], latlon[1], 8) -# Infer a mode from the frequency according to the band plan. Just a guess really. +# Infer a mode from the frequency (in Hz) according to the band plan. Just a guess really. def infer_mode_from_frequency(freq): - return freq_to_band(freq)["mode"] + try: + return freq_to_band(freq / 1000.0)["mode"] + except KeyError: + return None # Convert objects to serialisable things. Used by JSON serialiser as a default when it encounters unserializable things. diff --git a/data/band.py b/data/band.py index e57a790..011d97f 100644 --- a/data/band.py +++ b/data/band.py @@ -5,9 +5,9 @@ from dataclasses import dataclass class Band: # Band name name: str - # Start frequency, in kHz + # Start frequency, in Hz start_freq: float - # Stop frequency, in kHz + # Stop frequency, in Hz end_freq: float # Colour to use for this band, as per PSK Reporter color: str diff --git a/data/spot.py b/data/spot.py index 59e3851..944c2ce 100644 --- a/data/spot.py +++ b/data/spot.py @@ -53,7 +53,7 @@ class Spot: mode_type: str = None # Source of the mode information. "SPOT", "COMMENT", "BANDPLAN" or "NONE" mode_source: str = "NONE" - # Frequency, in kHz + # Frequency, in Hz freq: float = None # Band, defined by the frequency, e.g. "40m" or "70cm" band: str = None diff --git a/providers/dxcluster.py b/providers/dxcluster.py index 54e7735..a2a4b42 100644 --- a/providers/dxcluster.py +++ b/providers/dxcluster.py @@ -67,7 +67,7 @@ class DXCluster(Provider): spot = Spot(source=self.name, dx_call=match.group(3), de_call=match.group(1), - freq=float(match.group(2)), + freq=float(match.group(2)) * 1000, comment=match.group(4).strip(), icon="desktop", time=spot_datetime) diff --git a/providers/gma.py b/providers/gma.py index e5a98b8..1747948 100644 --- a/providers/gma.py +++ b/providers/gma.py @@ -27,7 +27,7 @@ class GMA(HTTPProvider): spot = Spot(source=self.name, dx_call=source_spot["ACTIVATOR"].upper(), de_call=source_spot["SPOTTER"].upper(), - freq=float(source_spot["QRG"]) if (source_spot["QRG"] != "") else None, # Seen GMA spots with no frequency + freq=float(source_spot["QRG"]) * 1000 if (source_spot["QRG"] != "") else None, # Seen GMA spots with no frequency mode=source_spot["MODE"].upper() if "<>" not in source_spot["MODE"] else None, # Filter out some weird mode strings comment=source_spot["TEXT"], sig_refs=[source_spot["REF"]], diff --git a/providers/hema.py b/providers/hema.py index 261ba8a..b12c6c9 100644 --- a/providers/hema.py +++ b/providers/hema.py @@ -48,7 +48,7 @@ class HEMA(HTTPProvider): spot = Spot(source=self.name, dx_call=spot_items[2].upper(), de_call=spotter_comment_match.group(1).upper(), - freq=float(freq_mode_match.group(1)) * 1000, + freq=float(freq_mode_match.group(1)) * 1000000, mode=freq_mode_match.group(2).upper(), comment=spotter_comment_match.group(2), sig="HEMA", diff --git a/providers/parksnpeaks.py b/providers/parksnpeaks.py index 5ece5c3..669ab5f 100644 --- a/providers/parksnpeaks.py +++ b/providers/parksnpeaks.py @@ -24,7 +24,7 @@ class ParksNPeaks(HTTPProvider): source_id=source_spot["actID"], dx_call=source_spot["actCallsign"].upper(), de_call=source_spot["actSpoter"].upper(), # typo exists in API - freq=float(source_spot["actFreq"]) * 1000 if (source_spot["actFreq"] != "") else None, # Seen PNP spots with empty frequency! + freq=float(source_spot["actFreq"]) * 1000000 if (source_spot["actFreq"] != "") else None, # Seen PNP spots with empty frequency! mode=source_spot["actMode"].upper(), comment=source_spot["actComments"], sig=source_spot["actClass"], diff --git a/providers/pota.py b/providers/pota.py index e5ba12d..0c2cdd7 100644 --- a/providers/pota.py +++ b/providers/pota.py @@ -23,7 +23,7 @@ class POTA(HTTPProvider): source_id=source_spot["spotId"], dx_call=source_spot["activator"].upper(), de_call=source_spot["spotter"].upper(), - freq=float(source_spot["frequency"]), + freq=float(source_spot["frequency"]) * 1000, mode=source_spot["mode"].upper(), comment=source_spot["comments"], sig="POTA", diff --git a/providers/rbn.py b/providers/rbn.py index 0a7b727..1222dc9 100644 --- a/providers/rbn.py +++ b/providers/rbn.py @@ -68,7 +68,7 @@ class RBN(Provider): spot = Spot(source=self.name, dx_call=match.group(3), de_call=match.group(1), - freq=float(match.group(2)), + freq=float(match.group(2)) * 1000, comment=match.group(4).strip(), icon="tower-cell", time=spot_datetime) diff --git a/providers/sota.py b/providers/sota.py index 9d0233b..298ddc2 100644 --- a/providers/sota.py +++ b/providers/sota.py @@ -42,7 +42,7 @@ class SOTA(HTTPProvider): dx_call=source_spot["activatorCallsign"].upper(), dx_name=source_spot["activatorName"], de_call=source_spot["callsign"].upper(), - freq=(float(source_spot["frequency"]) * 1000) if (source_spot["frequency"] is not None) else None, # Seen SOTA spots with no frequency! + freq=(float(source_spot["frequency"]) * 1000000) if (source_spot["frequency"] is not None) else None, # Seen SOTA spots with no frequency! mode=source_spot["mode"].upper(), comment=source_spot["comments"], sig="SOTA", diff --git a/providers/wwbota.py b/providers/wwbota.py index d9e9e00..f4a7693 100644 --- a/providers/wwbota.py +++ b/providers/wwbota.py @@ -26,7 +26,7 @@ class WWBOTA(HTTPProvider): spot = Spot(source=self.name, dx_call=source_spot["call"].upper(), de_call=source_spot["spotter"].upper(), - freq=float(source_spot["freq"]) * 1000, # MHz to kHz + freq=float(source_spot["freq"]) * 1000000, mode=source_spot["mode"].upper(), comment=source_spot["comment"], sig="WWBOTA", diff --git a/providers/wwff.py b/providers/wwff.py index 87a439f..d005fb9 100644 --- a/providers/wwff.py +++ b/providers/wwff.py @@ -23,7 +23,7 @@ class WWFF(HTTPProvider): source_id=source_spot["id"], dx_call=source_spot["activator"].upper(), de_call=source_spot["spotter"].upper(), - freq=float(source_spot["frequency_khz"]), + freq=float(source_spot["frequency_khz"]) * 1000, mode=source_spot["mode"].upper(), comment=source_spot["remarks"], sig="WWFF", diff --git a/views/webpage_home.tpl b/views/webpage_home.tpl index 77533c4..41a936d 100644 --- a/views/webpage_home.tpl +++ b/views/webpage_home.tpl @@ -7,10 +7,45 @@

- - + +

+ +
+
+
+
+ Status +
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+ Filters +
+
+ +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 2b37540..594d41a 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -430,8 +430,8 @@ components: - NONE freq: type: number - description: Frequency, in kHz - example: 7150.5 + description: Frequency, in Hz + example: 7150500 band: type: string description: Band, defined by the frequency. @@ -579,12 +579,12 @@ components: example: 40m start_freq: type: int - description: The start frequency of this band, in kHz. - example: 7000 + description: The start frequency of this band, in Hz. + example: 7000000 end_freq: type: int - description: The end frequency of this band, in kHz. - example: 7200 + description: The end frequency of this band, in Hz. + example: 7200000 color: type: string description: The color associated with this mode, as used on PSK Reporter. diff --git a/webassets/css/style.css b/webassets/css/style.css index f60e150..91086f4 100644 --- a/webassets/css/style.css +++ b/webassets/css/style.css @@ -47,4 +47,12 @@ span.mode-q { tr.table-faded td { color: lightgray; text-decoration: line-through !important; +} + +div.appearing-panel { + display: none; +} + +div.status-card { + max-width: 18rem; } \ No newline at end of file diff --git a/webassets/js/code.js b/webassets/js/code.js index 31faa9f..7a3a363 100644 --- a/webassets/js/code.js +++ b/webassets/js/code.js @@ -50,9 +50,9 @@ function updateTable() { } // Format the frequency - var mhz = Math.floor(s["freq"] / 1000.0); - var khz = Math.floor(s["freq"] - (mhz * 1000.0)); - var hz = Math.floor((s["freq"] - Math.floor(s["freq"])) * 1000.0); + var mhz = Math.floor(s["freq"] / 1000000.0); + var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0); + var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0)); var hz_string = (hz > 0) ? hz.toFixed(0) : ""; var freq_string = `${mhz.toFixed(0)}${khz.toFixed(0).padStart(3, '0')}${hz_string}` @@ -105,10 +105,44 @@ function updateTable() { // Load server status function loadStatus() { $.getJSON('/api/status', function(jsonData) { - $('#status-container').html(jsonData); // todo implement + $("#status-container").append(generateStatusCard("Server Information", [ + `Software Version: ${jsonData["software-version"]}`, + `Server Owner Callsign: ${jsonData["server-owner-callsign"]}`, + `Server Uptime: ${jsonData["uptime"]}`, + `Memory Use: ${jsonData["mem_use_mb"]} MB`, + `Total Spots: ${jsonData["num_spots"]}` + ])); + $("#status-container").append(generateStatusCard("Web Server", [ + `Status: ${jsonData["webserver"]["status"]}`, + `Last API Access: ${moment.utc(jsonData["webserver"]["last_api_access"], moment.ISO_8601).format("HH:mm")}`, + `Last Page Access: ${moment.utc(jsonData["webserver"]["last_page_access"], moment.ISO_8601).format("HH:mm")}` + ])); + $("#status-container").append(generateStatusCard("Cleanup Service", [ + `Status: ${jsonData["cleanup"]["status"]}`, + `Last Ran: ${moment.utc(jsonData["cleanup"]["last_ran"], moment.ISO_8601).format("HH:mm")}` + ])); + jsonData["providers"].forEach(p => { + $("#status-container").append(generateStatusCard("Provider: " + p["name"], [ + `Status: ${p["status"]}`, + `Last Updated: ${p["enabled"] ? moment.utc(p["last_updated"], moment.ISO_8601).format("HH:mm") : "N/A"}`, + `Latest Spot: ${p["enabled"] ? moment.utc(p["last_spot"], moment.ISO_8601).format("HH:mm YYYY-MM-DD") : "N/A"}` + ])); + }); }); } +// Generate a status card +function generateStatusCard(title, textLines) { + let $col = $("
") + let $card = $("
"); + let $card_body = $("
"); + $card_body.append(`
${title}
`); + $card_body.append(`

${textLines.join("
")}

`); + $card.append($card_body); + $col.append($card); + return $col; +} + // Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query // spots repeatedly. function loadOptions() { @@ -121,6 +155,9 @@ function loadOptions() { band_colors[m["name"]] = m["color"] }); + // Populate the filters panel + $("#filters-container").text(JSON.stringify(options)); + // Load spots and set up the timer loadSpots(); setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000) @@ -162,8 +199,22 @@ function escapeHtml(str) { return str.replace(/[&<>"'`]/g, escapeCharacter); } +// Startup +$(document).ready(function() { + // Call loadOptions(), this will then trigger loading spots and setting up timers. + loadOptions(); + // Update the refresh timing display every second + setInterval(updateRefreshDisplay, 1000); -// Startup. Call loadOptions(), this will then trigger loading spots and setting up timers. -loadOptions(); -// Update the refresh timing display every second -setInterval(updateRefreshDisplay, 1000); \ No newline at end of file + // Event listeners + $("#status-button").click(function() { + // If we are going to display status, load the data + if (!$("#status-area").is(":visible")) { + loadStatus(); + } + $("#status-area").toggle(); + }); + $("#close-status-button").click(function() { $("#status-area").hide(); }); + $("#filters-button").click(function() { $("#filters-area").toggle(); }); + $("#close-filters-button").click(function() { $("#filters-area").hide(); }); +}); \ No newline at end of file