// How often to query the server? const REFRESH_INTERVAL_SEC = 60; // Storage for the spot data that the server gives us. var spots = [] // Load spots and populate the table. function loadSpots() { $.getJSON('/api/spots' + buildQueryString(), function(jsonData) { // Store last updated time lastUpdateTime = moment.utc(); updateRefreshDisplay(); // Store data spots = jsonData; // Update table updateTable(); }); } // Build a query string for the API, based on the filters that the user has selected. function buildQueryString() { var str = "?"; ["dx_continent", "de_continent", "mode_type", "source", "band"].forEach(fn => { if (!allFilterOptionsSelected(fn)) { str = str + getQueryStringFor(fn) + "&"; } }); str = str + "limit=" + $("#spots-to-fetch option:selected").val(); return str; } // Update the spots table function updateTable() { // 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(``); table.find('thead tr').append(``); if (spots.length == 0) { table.find('tbody').append(''); } spots.forEach(s => { // Create row let $tr = $(''); // Show faded out if QRT if (s["qrt"] == true) { $tr.addClass("table-faded"); } // Format a UTC time for display var time = moment.unix(s["time"]).utc(); var time_formatted = time.format("HH:mm"); // Format DX flag var dx_flag = ""; if (s["dx_flag"] && s["dx_flag"] != null && s["dx_flag"] != "") { dx_flag = s["dx_flag"]; } // Format dx country var dx_country = s["dx_country"]; if (dx_country == null) { dx_country = "Unknown or not a country"; } // Format the frequency 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)[0] : ""; var freq_string = `${mhz.toFixed(0)}${khz.toFixed(0).padStart(3, '0')}${hz_string}` // Format the mode mode_string = s["mode"]; if (s["mode"] == null) { mode_string = "???"; } if (s["mode_source"] == "BANDPLAN") { mode_string = mode_string + "" } // Format comment var commentText = ""; if (s["comment"] != null) { commentText = escapeHtml(s["comment"]); } // Sig or fallback to source var sigSourceText = s["source"]; if (s["sig"]) { sigSourceText = s["sig"]; } // Format sig_refs var sig_refs = ""; if (s["sig_refs"]) { sig_refs = s["sig_refs"].map(s => `${s}`).join(", "); } // Format DE flag var de_flag = ""; if (s["de_flag"] && s["de_flag"] != null && s["de_flag"] != "") { de_flag = s["de_flag"]; } // Format de country var de_country = s["de_country"]; if (de_country == null) { de_country = "Unknown or not a country"; } // CSS doesn't like classes with decimal points in, so we need to replace that in the same way as when we originally // queried the options endpoint and set our CSS. var cssFormattedBandName = s['band'] ? s['band'].replace('.', 'p') : "unknown"; var bandFullName = s['band'] ? s['band'] + " band": "Unknown band"; // Populate the row $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); $tr.append(``); table.find('tbody').append($tr); // Second row for mobile view only, containing source, ref & comment $tr2 = $(""); if (s["qrt"] == true) { $tr2.addClass("table-faded"); } $tr2.append(``); table.find('tbody').append($tr2); }); // Update DOM $('#table-container').html(table); } // Load server status function loadStatus() { $.getJSON('/api/status', function(jsonData) { $("#status-container").empty(); $("#status-container").append(generateStatusCard("Server Information", [ `Software Version: ${jsonData["software-version"]}`, `Server Owner Callsign: ${jsonData["server-owner-callsign"]}`, `Server Uptime: ${moment().subtract(jsonData["uptime"], 'seconds').fromNow()}`, `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.unix(jsonData["webserver"]["last_api_access"]).utc().fromNow()}`, `Last Page Access: ${moment.unix(jsonData["webserver"]["last_page_access"]).utc().fromNow()}` ])); $("#status-container").append(generateStatusCard("Cleanup Service", [ `Status: ${jsonData["cleanup"]["status"]}`, `Last Ran: ${moment.unix(jsonData["cleanup"]["last_ran"]).utc().fromNow()}` ])); jsonData["spot_providers"].forEach(p => { $("#status-container").append(generateStatusCard("Provider: " + p["name"], [ `Status: ${p["status"]}`, `Last Updated: ${p["enabled"] ? moment.unix(p["last_updated"]).utc().fromNow() : "N/A"}`, `Latest Spot: ${p["enabled"] ? moment.unix(p["last_spot"]).utc().fromNow() : "N/A"}` ])); }); jsonData["alert_providers"].forEach(p => { $("#status-container").append(generateStatusCard("Provider: " + p["name"], [ `Status: ${p["status"]}`, `Last Updated: ${p["enabled"] ? moment.unix(p["last_updated"]).utc().fromNow() : "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() { $.getJSON('/api/options', function(jsonData) { // Store options options = jsonData; // Add CSS for band bullets and band toggle buttons 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"])); // Load settings from settings storage loadSettings(); // Load spots and set up the timer loadSpots(); setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000); }); } // Dynamically add CSS code for the band bullets and band toggle buttons to be in the appropriate colour. // Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p". function addBandColourCSS(band_options) { var $style = $('
UTCDXFrequencyModeCommentSourceRef.DE
No spots match your filters.
${time_formatted}${dx_flag}${s["dx_call"]}${freq_string}${mode_string}${commentText} ${sigSourceText}${sig_refs}${de_flag}${s["de_call"]}
${sig_refs} ${commentText}