mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Set up status and start working on filters panel #7
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -7,10 +7,45 @@
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="button">Filters</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="button">Status</button>
|
||||
<button id="filters-button" type="button" class="btn btn-primary">Filters</button>
|
||||
<button id="status-button" type="button" class="btn btn-primary">Status</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status-area" class="appearing-panel card mb-3">
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Status
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-status-button" type="button" class="btn-close btn-close-white" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-area" class="appearing-panel card mb-3">
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="filters-container" class="row row-cols-1 row-cols-md-2 g-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="table-container"></div>
|
||||
</div>
|
||||
@@ -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.
|
||||
|
||||
@@ -48,3 +48,11 @@ tr.table-faded td {
|
||||
color: lightgray;
|
||||
text-decoration: line-through !important;
|
||||
}
|
||||
|
||||
div.appearing-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.status-card {
|
||||
max-width: 18rem;
|
||||
}
|
||||
@@ -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 = `<span class='freq-mhz'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz'>${hz_string}</span>`
|
||||
|
||||
@@ -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 = $("<div class='col'>")
|
||||
let $card = $("<div class='card status-card'>");
|
||||
let $card_body = $("<div class='card-body'>");
|
||||
$card_body.append(`<h5 class='card-title'>${title}</h5>`);
|
||||
$card_body.append(`<p class='card-text'>${textLines.join("<br/>")}</p>`);
|
||||
$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);
|
||||
// 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(); });
|
||||
});
|
||||
Reference in New Issue
Block a user