mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
222 lines
9.6 KiB
JavaScript
222 lines
9.6 KiB
JavaScript
// How often to query the server?
|
|
const REFRESH_INTERVAL_SEC = 60 * 30;
|
|
|
|
// Storage for the alert data that the server gives us.
|
|
var alerts = []
|
|
|
|
// Load alerts and populate the table.
|
|
function loadAlerts() {
|
|
$.getJSON('/api/alerts' + buildQueryString(), function(jsonData) {
|
|
// Store last updated time
|
|
lastUpdateTime = moment.utc();
|
|
updateRefreshDisplay();
|
|
// Store data
|
|
alerts = 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", "source"].forEach(fn => {
|
|
if (!allFilterOptionsSelected(fn)) {
|
|
str = str + getQueryStringFor(fn) + "&";
|
|
}
|
|
});
|
|
str = str + "limit=" + $("#alerts-to-fetch option:selected").val();
|
|
var maxDur = $("#max-duration option:selected").val();
|
|
if (maxDur != "") {
|
|
str = str + "&max_duration=" + maxDur;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// Update the alerts table
|
|
function updateTable() {
|
|
// Populate table with headers
|
|
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>End UTC</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'>Comment</th>`);
|
|
table.find('thead tr').append(`<th class='hideonmobile'>Source</th>`);
|
|
table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
|
|
|
|
// Split alerts into three types, each of which will get its own table header: On now, next 24h, and later. "On now"
|
|
// is considered to be events with an end_time where start<now<end, or events with no end time that started in the
|
|
// last hour.
|
|
onNow = alerts.filter(a => (a["end_time"] != null && a["end_time"] != 0 && moment.unix(a["end_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore())
|
|
|| ((a["end_time"] == null || a["end_time"] == 0) && moment.unix(a["start_time"]).utc().add(1, 'hours').isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore()));
|
|
next24h = alerts.filter(a => moment.unix(a["start_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().subtract(24, 'hours').isBefore());
|
|
later = alerts.filter(a => moment.unix(a["start_time"]).utc().subtract(24, 'hours').isSameOrAfter());
|
|
|
|
alerts.forEach(a => {
|
|
if (!(a in onNow) && !(a in next24h) && !(a in later)) {
|
|
console.log(moment.unix(a["start_time"]).format('YYYY-MM-DD hh:mm') + " " + moment.unix(a["end_time"]).format('YYYY-MM-DD hh:mm'));
|
|
}
|
|
});
|
|
|
|
if (onNow.length > 0) {
|
|
table.find('tbody').append('<tr class="table-primary"><td colspan="100" style="text-align:center;">On Now</td></tr>');
|
|
addAlertRowsToTable(table.find('tbody'), onNow);
|
|
}
|
|
|
|
if (next24h.length > 0) {
|
|
table.find('tbody').append('<tr class="table-primary"><td colspan="100" style="text-align:center;">Starting within 24 hours</td></tr>');
|
|
addAlertRowsToTable(table.find('tbody'), next24h);
|
|
}
|
|
|
|
if (later.length > 0) {
|
|
table.find('tbody').append('<tr class="table-primary"><td colspan="100" style="text-align:center;">Starting later </td></tr>');
|
|
addAlertRowsToTable(table.find('tbody'), later);
|
|
}
|
|
|
|
if (onNow.length == 0 && next24h.length == 0 && later.length == 0) {
|
|
table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No alerts match your filters.</td></tr>');
|
|
}
|
|
|
|
// Update DOM
|
|
$('#table-container').html(table);
|
|
}
|
|
|
|
// Add a row to tbody for each alert in the provided list
|
|
function addAlertRowsToTable(tbody, alerts) {
|
|
alerts.forEach(a => {
|
|
// Create row
|
|
let $tr = $('<tr>');
|
|
|
|
// Format UTC times for display
|
|
var start_time = moment.unix(a["start_time"]).utc();
|
|
var start_time_formatted = start_time.format("YYYY-MM-DD HH:mm");
|
|
var end_time_unix = moment.unix(a["end_time"]);
|
|
var end_time = end_time_unix.utc();
|
|
var end_time_formatted = (end_time_unix != null && end_time_unix > 0 && end_time != null) ? end_time.format("YYYY-MM-DD HH:mm") : "---";
|
|
|
|
// Format DX flag
|
|
var dx_flag = "<i class='fa-solid fa-circle-question'></i>";
|
|
if (a["dx_flag"] && a["dx_flag"] != null && a["dx_flag"] != "") {
|
|
dx_flag = a["dx_flag"];
|
|
}
|
|
|
|
// Format dx country
|
|
var dx_country = a["dx_country"]
|
|
if (dx_country == null) {
|
|
dx_country = "Unknown or not a country"
|
|
}
|
|
|
|
// Format freqs & modes
|
|
var freqsModesText = "";
|
|
if (a["freqs_modes"] != null) {
|
|
freqsModesText = escapeHtml(a["freqs_modes"]);
|
|
}
|
|
|
|
// Format comment
|
|
var commentText = "";
|
|
if (a["comment"] != null) {
|
|
commentText = escapeHtml(a["comment"]);
|
|
}
|
|
|
|
// Sig or fallback to source
|
|
var sigSourceText = a["source"];
|
|
if (a["sig"]) {
|
|
sigSourceText = a["sig"];
|
|
}
|
|
|
|
// Format sig_refs
|
|
var sig_refs = ""
|
|
if (a["sig_refs"]) {
|
|
sig_refs = a["sig_refs"].map(a => `<span class='nowrap'>${a}</span>`).join(", ");
|
|
}
|
|
|
|
// Populate the row
|
|
$tr.append(`<td class='nowrap'>${start_time_formatted}</td>`);
|
|
$tr.append(`<td class='nowrap'>${end_time_formatted}</td>`);
|
|
$tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${dx_flag}</span><a class='dx-link' href='https://qrz.com/db/${a["dx_call"]}' target='_new'>${a["dx_call"]}</a></td>`);
|
|
$tr.append(`<td class='hideonmobile'>${freqsModesText}</td>`);
|
|
$tr.append(`<td class='hideonmobile'>${commentText}</td>`);
|
|
$tr.append(`<td class='nowrap hideonmobile'><span class='icon-wrapper'><i class='fa-solid fa-${a["icon"]}'></i></span> ${sigSourceText}</td>`);
|
|
$tr.append(`<td class='hideonmobile'>${sig_refs}</td>`);
|
|
tbody.append($tr);
|
|
|
|
// Second row for mobile view only, containing source, ref, freqs/modes & comment
|
|
$tr2 = $("<tr class='hidenotonmobile'>");
|
|
$tr2.append(`<td colspan="100"><span class='icon-wrapper'><i class='fa-solid fa-${a["icon"]}'></i></span> ${sig_refs} ${freqsModesText}<br/>${commentText}</td>`);
|
|
tbody.append($tr2);
|
|
});
|
|
}
|
|
|
|
// Load server options. Once a successful callback is made from this, we then query alerts.
|
|
function loadOptions() {
|
|
$.getJSON('/api/options', function(jsonData) {
|
|
// Store options
|
|
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"]));
|
|
|
|
// 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"]));
|
|
|
|
// Load settings from settings storage
|
|
loadSettings();
|
|
|
|
// Load alerts and set up the timer
|
|
loadAlerts();
|
|
setInterval(loadAlerts, REFRESH_INTERVAL_SEC * 1000);
|
|
});
|
|
}
|
|
|
|
// Generate maximum duration drop-down filter card. This one is a special case.
|
|
function generateMaxDurationDropdownFilterCard(band_options) {
|
|
let $col = $("<div class='col'>")
|
|
let $card = $("<div class='card'>");
|
|
let $card_body = $("<div class='card-body'>");
|
|
$card_body.append(`<h5 class='card-title'>Duration Limit</h5>`);
|
|
$p = $("<p class='card-text filter-card-text'>");
|
|
$p.append("Hide any alerts lasting more than:<br/>");
|
|
$p.append(`<select id="max-duration" class="storeable-select form-select" onclick="filtersUpdated();" style="width: 8em; display: inline-block;">
|
|
<option value="10800">3 hours</option>
|
|
<option value="43200">12 hours</option>
|
|
<option value="86400" selected>24 hours</option>
|
|
<option value="604800">1 week</option>
|
|
<option value="2419200">4 weeks</option>
|
|
<option value="">No limit</option>
|
|
</select>`);
|
|
$p.append(" <i class='fa-solid fa-circle-question' title='Some users create long-duration alerts for the period they will be generally in and around xOTA references, not just the times they are specifically on the air. Use this control to restrict the maximum duration of spots that the software will display, and exclude any with a long duration.'></i>");
|
|
// Compile HTML elements to return
|
|
$card_body.append($p);
|
|
$card.append($card_body);
|
|
$col.append($card);
|
|
return $col;
|
|
}
|
|
|
|
// Method called when any filter is changed to reload the alerts and persist the filter settings.
|
|
function filtersUpdated() {
|
|
loadAlerts();
|
|
saveSettings();
|
|
}
|
|
// Set up UI element event listeners, after the document is ready
|
|
function setUpEventListeners() {
|
|
$("#settings-button").click(function() {
|
|
$("#settings-area").toggle();
|
|
});
|
|
$("#close-settings-button").click(function() {
|
|
$("#settings-button").button("toggle");
|
|
$("#settings-area").hide();
|
|
});
|
|
}
|
|
|
|
// Startup
|
|
$(document).ready(function() {
|
|
// Call loadOptions(), this will then trigger loading alerts and setting up timers.
|
|
loadOptions();
|
|
// Update the refresh timing display every second
|
|
setInterval(updateRefreshDisplay, 1000);
|
|
// Set up event listeners
|
|
setUpEventListeners();
|
|
}); |