mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Further alert implementation #17
This commit is contained in:
@@ -199,6 +199,12 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
- name: max_duration
|
||||
in: query
|
||||
description: Limit the spots to only ones with a duration of this many seconds or less. Duration is end time minus start time, if end time is set, otherwise "now" minus start time. This is useful to filter out people who alert POTA activations lasting months or even years.
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
- name: source
|
||||
in: query
|
||||
description: "Limit the spots to only ones from one or more sources. To select more than one source, supply a comma-separated list."
|
||||
@@ -424,9 +430,9 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type: string
|
||||
description: Unique identifier based on a hash of the spot to distinguish this one from any others.
|
||||
example: 123987609816349182
|
||||
example: 442c5d56ac467341f1943e8596685073b38f5a5d4c3802ca1e16ecf98967956c
|
||||
dx_call:
|
||||
type: string
|
||||
description: Callsign of the operator that has been spotted
|
||||
@@ -675,9 +681,9 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type: string
|
||||
description: Unique identifier based on a hash of the alert to distinguish this one from any others.
|
||||
example: 123987609816349182
|
||||
example: 442c5d56ac467341f1943e8596685073b38f5a5d4c3802ca1e16ecf98967956c
|
||||
dx_call:
|
||||
type: string
|
||||
description: Callsign of the operator that is going to be activating
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.navbar-nav .nav-link.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#info-container{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// How often to query the server?
|
||||
const REFRESH_INTERVAL_SEC = 60 * 30;
|
||||
|
||||
// Storage for the alert data that the server gives us.
|
||||
var alerts = []
|
||||
// Storage for the options that the server gives us. This will define our filters.
|
||||
@@ -8,8 +11,9 @@ var lastUpdateTime;
|
||||
// Load alerts and populate the table.
|
||||
function loadAlerts() {
|
||||
$.getJSON('/api/alerts' + buildQueryString(), function(jsonData) {
|
||||
// Present loaded time
|
||||
$("#timing-container").text("Data loaded at " + moment.utc().format('HH:mm') + " UTC.");
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
updateRefreshDisplay();
|
||||
// Store data
|
||||
alerts = jsonData;
|
||||
// Update table
|
||||
@@ -26,6 +30,7 @@ function buildQueryString() {
|
||||
}
|
||||
});
|
||||
str = str + "limit=" + $("#alerts-to-fetch option:selected").val();
|
||||
str = str + "&max_duration=604800";
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -64,71 +69,94 @@ function updateTable() {
|
||||
table.find('thead tr').append(`<th class='hideonmobile'>Source</th>`);
|
||||
table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
|
||||
|
||||
if (alerts.length == 0) {
|
||||
// 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 && moment.unix(a["end_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore())
|
||||
|| (a["end_time"] == null && 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());
|
||||
|
||||
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>');
|
||||
}
|
||||
|
||||
alerts.forEach(s => {
|
||||
// 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(s["start_time"]).utc();
|
||||
var start_time = moment.unix(a["start_time"]).utc();
|
||||
var start_time_formatted = start_time.format("YYYY-MM-DD HH:mm");
|
||||
var end_time = moment.unix(s["start_time"]).utc();
|
||||
var end_time = moment.unix(a["end_time"]).utc();
|
||||
var end_time_formatted = (end_time != null) ? end_time.format("YYYY-MM-DD HH:mm") : "Not specified";
|
||||
|
||||
// Format dx country
|
||||
var dx_country = s["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 (s["freqs_modes"] != null) {
|
||||
freqsModesText = escapeHtml(s["freqs_modes"]);
|
||||
if (a["freqs_modes"] != null) {
|
||||
freqsModesText = escapeHtml(a["freqs_modes"]);
|
||||
}
|
||||
|
||||
// Format comment
|
||||
var commentText = "";
|
||||
if (s["comment"] != null) {
|
||||
commentText = escapeHtml(s["comment"]);
|
||||
if (a["comment"] != null) {
|
||||
commentText = escapeHtml(a["comment"]);
|
||||
}
|
||||
|
||||
// Sig or fallback to source
|
||||
var sigSourceText = s["source"];
|
||||
if (s["sig"]) {
|
||||
sigSourceText = s["sig"];
|
||||
var sigSourceText = a["source"];
|
||||
if (a["sig"]) {
|
||||
sigSourceText = a["sig"];
|
||||
}
|
||||
|
||||
// Format sig_refs
|
||||
var sig_refs = ""
|
||||
if (s["sig_refs"]) {
|
||||
sig_refs = s["sig_refs"].join(", ")
|
||||
if (a["sig_refs"]) {
|
||||
sig_refs = a["sig_refs"].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}'>${s["dx_flag"]}</span><a class='dx-link' href='https://qrz.com/db/${s["dx_call"]}' target='_new'>${s["dx_call"]}</a></td>`);
|
||||
$tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${a["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-${s["icon"]}'></i></span> ${sigSourceText}</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>`);
|
||||
table.find('tbody').append($tr);
|
||||
tbody.append($tr);
|
||||
|
||||
// Second row for mobile view only, containing source, ref, freqs/modes & comment
|
||||
$tr2 = $("<tr class='hidenotonmobile'>");
|
||||
if (s["qrt"] == true) {
|
||||
$tr2.addClass("table-faded");
|
||||
}
|
||||
$tr2.append(`<td colspan="100"><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${sig_refs} ${freqsModesText}<br/>${commentText}</td>`);
|
||||
table.find('tbody').append($tr2);
|
||||
$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);
|
||||
});
|
||||
|
||||
// Update DOM
|
||||
$('#table-container').html(table);
|
||||
}
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query alerts.
|
||||
@@ -144,8 +172,9 @@ function loadOptions() {
|
||||
// Load settings from settings storage
|
||||
loadSettings();
|
||||
|
||||
// Load alerts
|
||||
// Load alerts and set up the timer
|
||||
loadAlerts();
|
||||
setInterval(loadAlerts, REFRESH_INTERVAL_SEC * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,6 +212,24 @@ function filtersUpdated() {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Update the refresh timing display
|
||||
function updateRefreshDisplay() {
|
||||
if (lastUpdateTime != null) {
|
||||
let count = REFRESH_INTERVAL_SEC;
|
||||
let secSinceUpdate = moment.duration(moment().diff(lastUpdateTime)).asSeconds();
|
||||
updatingString = "Updating..."
|
||||
if (secSinceUpdate < REFRESH_INTERVAL_SEC) {
|
||||
count = REFRESH_INTERVAL_SEC - secSinceUpdate;
|
||||
if (count <= 60) {
|
||||
updatingString = "Updating in " + count.toFixed(0) + " seconds...";
|
||||
} else {
|
||||
updatingString = "Updating in " + Math.floor(count / 60.0).toFixed(0) + " minutes.";
|
||||
}
|
||||
}
|
||||
$("#timing-container").text("Last updated at " + lastUpdateTime.format('HH:mm') + " UTC. " + updatingString);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to escape HTML characters from a string.
|
||||
function escapeHtml(str) {
|
||||
if (typeof str !== 'string') {
|
||||
@@ -244,6 +291,8 @@ function setUpEventListeners() {
|
||||
$(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();
|
||||
});
|
||||
@@ -314,7 +314,11 @@ function updateRefreshDisplay() {
|
||||
updatingString = "Updating..."
|
||||
if (secSinceUpdate < REFRESH_INTERVAL_SEC) {
|
||||
count = REFRESH_INTERVAL_SEC - secSinceUpdate;
|
||||
updatingString = "Updating in " + count.toFixed(0) + " seconds...";
|
||||
if (count <= 60) {
|
||||
updatingString = "Updating in " + count.toFixed(0) + " seconds...";
|
||||
} else {
|
||||
updatingString = "Updating in " + Math.floor(count / 60.0).toFixed(0) + " minutes.";
|
||||
}
|
||||
}
|
||||
$("#timing-container").text("Last updated at " + lastUpdateTime.format('HH:mm') + " UTC. " + updatingString);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user