mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-24 16:24:32 +00:00
First attempt at converting "sig" to "activity" for v3
This commit is contained in:
+67
-49
@@ -15,6 +15,25 @@ info:
|
||||
|
||||
## Changelog
|
||||
|
||||
### 3.0
|
||||
|
||||
The term "SIG" (Special Interest Group), which Spothole inherited from ADIF, has been replaced with "activity" throughout the API.
|
||||
|
||||
* **Breaking change:** In spot and alert data, `sig` has been renamed to `activity` and `sig_refs` to `activity_refs`.
|
||||
* **Breaking change:** In activity reference data (i.e. each entry in `activity_refs`, and the response of the activity reference lookup), `sig` has been renamed to `activity`.
|
||||
* **Breaking change:** The `dx_location_source` value "SIG REF LOOKUP" has been renamed to "ACTIVITY REF LOOKUP".
|
||||
* **Breaking change:** The `/spots`, `/spots/stream`, `/alerts` and `/alerts/stream` query parameter `sig` has been renamed to `activity`, and its special value `NO_SIG` to `NO_ACTIVITY`. The `/spots` and `/spots/stream` query parameters `needs_sig` and `needs_sig_ref` have been renamed to `needs_activity` and `needs_activity_ref`. When using the `fields` query parameter, use the new field names `activity` and `activity_refs`.
|
||||
* **Breaking change:** `/lookup/sigref` has been renamed to `/lookup/activityref`, and its `sig` query parameter has been renamed to `activity`.
|
||||
* **Breaking change:** POST `/spot` now expects `activity` and `activity_refs` in the `spot` object, rather than `sig` and `sig_refs`.
|
||||
* **Breaking change:** In the `/options` response, `sigs` has been renamed to `activities`, and within each activity, `sig_type` has been renamed to `activity_type`.
|
||||
* **Breaking change:** In the `/status` response, `sig_ref_data_providers` has been renamed to `activity_ref_data_providers`, and within each provider, `sig_name` has been renamed to `activity_name`.
|
||||
|
||||
#### Upgrading a client from v2 to v3 API endpoints
|
||||
|
||||
In v3.0 of Spothole, the `v2` (and `v1`) API endpoints will be maintained for backwards compatibility, so if you have written a client against the `v2` API, it will continue to receive `sig`, `sig_refs` etc. as before. However, you are encouraged to move to the `v3` API endpoints as soon as possible.
|
||||
|
||||
To upgrade, replace `v2` with `v3` in the URLs your code calls, then rename any use of the fields, query parameters and values listed above. If you use the activity reference lookup, call `/lookup/activityref?activity=...&id=...` instead of `/lookup/sigref?sig=...&id=...`.
|
||||
|
||||
### 2.2
|
||||
|
||||
* Renamed AMSAT SIG to "Satellite" as AMSAT is a specific organisation not just a general term for satellite QSOs
|
||||
@@ -102,10 +121,10 @@ info:
|
||||
license:
|
||||
name: The Unlicense
|
||||
url: https://unlicense.org/#the-unlicense
|
||||
version: 2.0
|
||||
version: 3.0
|
||||
|
||||
servers:
|
||||
- url: https://spothole.app/api/v2
|
||||
- url: https://spothole.app/api/v3
|
||||
|
||||
tags:
|
||||
- name: Spots
|
||||
@@ -396,7 +415,7 @@ paths:
|
||||
example: "Failed"
|
||||
|
||||
|
||||
/lookup/sigref:
|
||||
/lookup/activityref:
|
||||
get:
|
||||
tags:
|
||||
- Utilities
|
||||
@@ -405,7 +424,7 @@ paths:
|
||||
Perform a lookup of data about a certain reference, providing the activity and the ID of the
|
||||
reference. An ActivityRef structure will be returned containing the activity and ID, plus any other
|
||||
information Spothole could find about it.
|
||||
operationId: sigref
|
||||
operationId: activityref
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ActivityRefLookupActivity'
|
||||
- $ref: '#/components/parameters/ActivityRefLookupId'
|
||||
@@ -461,7 +480,7 @@ paths:
|
||||
Supply a JSON object containing a `spot` sub-object (the spot data) and an optional `handling` sub-object
|
||||
containing server-side instructions such as upstream submission). Check `spot_submit_providers` in the
|
||||
`/options` response to see which activities and providers support upstream submission. cURL example:
|
||||
`curl --request POST --header \"Content-Type: application/json\" --data '{\"spot\":{\"dx_call\":\"M0TRT\",\"time\":1760019539,\"freq\":14200000,\"comment\":\"Test spot please ignore\",\"de_call\":\"M0TRT\"}}' https://spothole.app/api/v2/spot`"
|
||||
`curl --request POST --header \"Content-Type: application/json\" --data '{\"spot\":{\"dx_call\":\"M0TRT\",\"time\":1760019539,\"freq\":14200000,\"comment\":\"Test spot please ignore\",\"de_call\":\"M0TRT\"}}' https://spothole.app/api/v3/spot`"
|
||||
operationId: spot
|
||||
requestBody:
|
||||
description: Object containing a "spot" sub-object with the spot data, and an optional "handling" sub-object with server-side instructions of what to do with it.
|
||||
@@ -559,32 +578,32 @@ components:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Source"
|
||||
SpotActivity:
|
||||
name: sig
|
||||
name: activity
|
||||
in: query
|
||||
description: >
|
||||
Limit the spots to only ones from one or more activities provided as an argument.
|
||||
To select more than one activity, supply a comma-separated list. The special `sig` name `NO_SIG`
|
||||
matches spots with no activity set. You can use `sig=NO_SIG` to specifically only return generic
|
||||
To select more than one activity, supply a comma-separated list. The special `activity` name `NO_ACTIVITY`
|
||||
matches spots with no activity set. You can use `activity=NO_ACTIVITY` to specifically only return generic
|
||||
spots with no associated activity. You can also use combinations to request for example POTA + no
|
||||
activity, but reject other activities. If you want to request 'every activity and not No Activity', see the
|
||||
`needs_sig` query parameter for a shortcut.
|
||||
`needs_activity` query parameter for a shortcut.
|
||||
schema:
|
||||
$ref: "#/components/schemas/ActivityNameIncludingNoSig"
|
||||
$ref: "#/components/schemas/ActivityNameIncludingNoActivity"
|
||||
SpotNeedsActivity:
|
||||
name: needs_sig
|
||||
name: needs_activity
|
||||
in: query
|
||||
description: >
|
||||
Limit the spots to only ones with an activity such as POTA. Because supplying all
|
||||
known activities as a `sigs` parameter is unwieldy, and leaving `sigs` blank will also return spots
|
||||
known activities as an `activity` parameter is unwieldy, and leaving `activity` blank will also return spots
|
||||
with *no* activity, this parameter can be set true to return only spots with an activity, regardless of
|
||||
what it is, so long as it's not blank. This is the equivalent of supplying the `sig` query
|
||||
param with a list of every known activity apart from the special `NO_SIG` value. This is what Field
|
||||
what it is, so long as it's not blank. This is the equivalent of supplying the `activity` query
|
||||
param with a list of every known activity apart from the special `NO_ACTIVITY` value. This is what Field
|
||||
Spotter uses to exclude generic cluster spots and only retrieve xOTA things.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
SpotNeedsActivityRef:
|
||||
name: needs_sig_ref
|
||||
name: needs_activity_ref
|
||||
in: query
|
||||
description: >
|
||||
Limit the spots to only ones which have at least one reference (e.g. a park reference) for
|
||||
@@ -723,14 +742,14 @@ components:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Source"
|
||||
AlertActivity:
|
||||
name: sig
|
||||
name: activity
|
||||
in: query
|
||||
description: >
|
||||
Limit the alerts to only ones from one or more activities. To select more than one
|
||||
activity, supply a comma-separated list. The special value 'NO_SIG' can be included to return alerts
|
||||
specifically without an associated activity (i.e. general DXpeditions).
|
||||
activity, supply a comma-separated list. The special value 'NO_ACTIVITY' can be included to return alerts
|
||||
specifically without an associated activity.
|
||||
schema:
|
||||
$ref: "#/components/schemas/ActivityNameIncludingNoSig"
|
||||
$ref: "#/components/schemas/ActivityNameIncludingNoActivity"
|
||||
AlertDxContinent:
|
||||
name: dx_continent
|
||||
in: query
|
||||
@@ -839,9 +858,9 @@ components:
|
||||
type: string
|
||||
example: M0TRT
|
||||
ActivityRefLookupActivity:
|
||||
name: sig
|
||||
name: activity
|
||||
in: query
|
||||
description: Activity, e.g. outdoor activity programme such as POTA (still named "sig" in the API for backwards compatibility)
|
||||
description: Activity, e.g. outdoor activity programme such as POTA
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/components/schemas/ActivityName"
|
||||
@@ -938,11 +957,11 @@ components:
|
||||
- EVENT
|
||||
example: TRADITIONAL
|
||||
|
||||
ActivityNameIncludingNoSig:
|
||||
ActivityNameIncludingNoActivity:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ActivityName"
|
||||
- type: string
|
||||
enum: [ NO_SIG ]
|
||||
enum: [ NO_ACTIVITY ]
|
||||
example: POTA
|
||||
|
||||
ActivityRefType:
|
||||
@@ -1068,7 +1087,7 @@ components:
|
||||
type: string
|
||||
enum:
|
||||
- SPOT
|
||||
- "SIG REF LOOKUP"
|
||||
- "ACTIVITY REF LOOKUP"
|
||||
- "GRID"
|
||||
- "HOME QTH"
|
||||
- DXCC
|
||||
@@ -1090,8 +1109,8 @@ components:
|
||||
type: string
|
||||
description: Activity reference ID.
|
||||
example: GB-0001
|
||||
sig:
|
||||
description: Activity that this reference is in. Still named "sig" in the API for backwards compatibility.
|
||||
activity:
|
||||
description: Activity that this reference is in.
|
||||
$ref: "#/components/schemas/ActivityName"
|
||||
name:
|
||||
type: string
|
||||
@@ -1205,7 +1224,7 @@ components:
|
||||
itself, or from a lookup of the activity ref (e.g. park) it's likely quite accurate, but if
|
||||
we had to fall back to QRZ lookup, or even a location based on the DXCC itself, it will
|
||||
be a lot less accurate. "SPOT" indicates the location source was the spot itself from the
|
||||
spotting service. "SIG REF LOOKUP" indicates that the spot didn't provide a location,
|
||||
spotting service. "ACTIVITY REF LOOKUP" indicates that the spot didn't provide a location,
|
||||
but we looked it up from reference data. "GRID" indicates that the spot provided some
|
||||
location data such as a Maidenhead, UK Ordnance Survey or Irish grid reference, but the
|
||||
location is likely less accurate than "SPOT". "HOME QTH" indicates we looked up the DX
|
||||
@@ -1218,7 +1237,7 @@ components:
|
||||
type: boolean
|
||||
description: >
|
||||
Does the software think the location is good enough to put a marker on a map? This is
|
||||
true if the source is "SPOT", "SIG REF LOOKUP" or "GRID", or alternatively if
|
||||
true if the source is "SPOT", "ACTIVITY REF LOOKUP" or "GRID", or alternatively if
|
||||
the source is "HOME QTH" and the callsign doesn't have a slash in it (i.e. operator
|
||||
likely at home).
|
||||
example: true
|
||||
@@ -1315,14 +1334,14 @@ components:
|
||||
type: string
|
||||
description: Comment left by the spotter, if any
|
||||
example: "59 in NY 73"
|
||||
sig:
|
||||
description: Activity, e.g. outdoor activity programme such as POTA (still named "sig" in the API for backwards compatibility)
|
||||
activity:
|
||||
description: Activity, e.g. outdoor activity programme such as POTA
|
||||
$ref: "#/components/schemas/ActivityName"
|
||||
sig_refs:
|
||||
activity_refs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ActivityRef'
|
||||
description: Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO. Still named "sig_refs" in the API for backwards compatibility.
|
||||
description: Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO.
|
||||
qrt:
|
||||
type: boolean
|
||||
description: QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
|
||||
@@ -1364,7 +1383,7 @@ components:
|
||||
type: boolean
|
||||
description: >
|
||||
If true, forward the spot to an external upstream provider (e.g. POTA, SOTA) rather
|
||||
than only adding it to this Spothole server. Requires `sig`, at least one `sig_refs`
|
||||
than only adding it to this Spothole server. Requires `activity`, at least one `activity_refs`
|
||||
entry, and `upstream_provider` to be set. Check `spot_submit_providers` in the
|
||||
/options response to see which activities and providers support this.
|
||||
default: false
|
||||
@@ -1503,14 +1522,14 @@ components:
|
||||
type: string
|
||||
description: Comment made by the activator, if any
|
||||
example: "2025 DXpedition to null island"
|
||||
sig:
|
||||
description: Activity, e.g. outdoor activity programme such as POTA (still named "sig" in the API for backwards compatibility)
|
||||
activity:
|
||||
description: Activity, e.g. outdoor activity programme such as POTA
|
||||
$ref: "#/components/schemas/ActivityName"
|
||||
sig_refs:
|
||||
activity_refs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ActivityRef'
|
||||
description: Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO. Still named "sig_refs" in the API for backwards compatibility.
|
||||
description: Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO.
|
||||
url:
|
||||
type: string
|
||||
description: A URL linking to more information about the alert, e.g. DXpedition or contest info.
|
||||
@@ -1604,8 +1623,8 @@ components:
|
||||
Activity:
|
||||
type: object
|
||||
description: >
|
||||
Represents an activity (a term which replaces the older "Special Interest Group" or "SIG" terminology,
|
||||
though `sig`-prefixed field names remain for API backwards compatibility).
|
||||
Represents an activity, such as an outdoor activity programme (e.g. POTA), or another kind of operating
|
||||
(e.g. Contest, DXpedition).
|
||||
properties:
|
||||
name:
|
||||
description: The abbreviated name of the activity
|
||||
@@ -1614,12 +1633,11 @@ components:
|
||||
type: string
|
||||
description: The full name of the activity
|
||||
example: Parks on the Air
|
||||
sig_type:
|
||||
type: boolean
|
||||
activity_type:
|
||||
description: >
|
||||
Whether the activity is traditional (e.g. EME), adventure (e.g. POTA), regional (e.g. WAB), or for a
|
||||
specific event (e.g. MOTA). Generally for Spothole's own internal use, clients probably won't need this.
|
||||
Used to group them in the web UI. Still named "sig_type" in the API for backwards compatibility.
|
||||
Used to group them in the web UI.
|
||||
$ref: "#/components/schemas/ActivityType"
|
||||
has_refs:
|
||||
type: boolean
|
||||
@@ -1998,7 +2016,7 @@ components:
|
||||
StaticDataProviderStatus:
|
||||
type: object
|
||||
properties:
|
||||
sig_name:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the provider.
|
||||
example: K0SWE
|
||||
@@ -2020,9 +2038,9 @@ components:
|
||||
ActivityRefDataProviderStatus:
|
||||
type: object
|
||||
properties:
|
||||
sig_name:
|
||||
activity_name:
|
||||
type: string
|
||||
description: The name of the activity. Still named "sig_name" in the API for backwards compatibility.
|
||||
description: The name of the activity.
|
||||
example: WWFF
|
||||
enabled:
|
||||
type: boolean
|
||||
@@ -2046,7 +2064,7 @@ components:
|
||||
CallsignDataProviderStatus:
|
||||
type: object
|
||||
properties:
|
||||
sig_name:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the provider.
|
||||
example: Country Files
|
||||
@@ -2185,7 +2203,7 @@ components:
|
||||
description: An array of all the static reference data providers.
|
||||
items:
|
||||
$ref: '#/components/schemas/StaticDataProviderStatus'
|
||||
sig_ref_data_providers:
|
||||
activity_ref_data_providers:
|
||||
type: array
|
||||
description: An array of all the activity reference data providers.
|
||||
items:
|
||||
@@ -2216,7 +2234,7 @@ components:
|
||||
items:
|
||||
type: string
|
||||
example: "PHONE"
|
||||
sigs:
|
||||
activities:
|
||||
type: array
|
||||
description: An array of all the supported activities.
|
||||
items:
|
||||
@@ -2254,7 +2272,7 @@ components:
|
||||
type: integer
|
||||
description: >
|
||||
The maximum age, in seconds, of any spot before it will be deleted by the system. When
|
||||
querying the /api/v2/spots endpoint and providing a "max_age" or "since" parameter, there
|
||||
querying the /api/v3/spots endpoint and providing a "max_age" or "since" parameter, there
|
||||
is no point providing a number larger than this, because the system drops all spots older
|
||||
than this.
|
||||
example: 3600
|
||||
|
||||
+13
-13
@@ -29,7 +29,7 @@ const PROVIDER_CREDENTIAL_SCHEMAS = {
|
||||
// Load server options. Once a successful callback is made from this, we can populate the choice boxes in the form and load
|
||||
// any saved values from local storage.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
@@ -42,8 +42,8 @@ function loadOptions() {
|
||||
});
|
||||
|
||||
// Populate activity drop-down
|
||||
$.each(options["sigs"], function (i, activity) {
|
||||
$('#sig').append($('<option>', {
|
||||
$.each(options["activities"], function (i, activity) {
|
||||
$('#activity').append($('<option>', {
|
||||
value: activity.name,
|
||||
text: activity.name
|
||||
}));
|
||||
@@ -91,8 +91,8 @@ function updateUpstreamArea() {
|
||||
return;
|
||||
}
|
||||
|
||||
const sig = $("#sig").val();
|
||||
const providers = (sig && options["spot_submit_providers"][sig]) ? options["spot_submit_providers"][sig] : [];
|
||||
const activity = $("#activity").val();
|
||||
const providers = (activity && options["spot_submit_providers"][activity]) ? options["spot_submit_providers"][activity] : [];
|
||||
|
||||
if (providers.length === 0) {
|
||||
$("#upstream-area").hide();
|
||||
@@ -131,8 +131,8 @@ function updateCredentialsButton() {
|
||||
|
||||
// Get the currently selected upstream provider name
|
||||
function getSelectedUpstreamProvider() {
|
||||
const providers = (options && options["spot_submit_providers"] && $("#sig").val())
|
||||
? (options["spot_submit_providers"][$("#sig").val()] || [])
|
||||
const providers = (options && options["spot_submit_providers"] && $("#activity").val())
|
||||
? (options["spot_submit_providers"][$("#activity").val()] || [])
|
||||
: [];
|
||||
if (providers.length === 0) return null;
|
||||
if (providers.length === 1) return providers[0];
|
||||
@@ -197,8 +197,8 @@ function addSpot() {
|
||||
const dx = $("#dx-call").val().toUpperCase();
|
||||
const freqStr = $("#freq").val();
|
||||
const mode = $("#mode")[0].value;
|
||||
const sig = $("#sig")[0].value;
|
||||
const sigRef = $("#sig-ref").val();
|
||||
const activity = $("#activity")[0].value;
|
||||
const activityRef = $("#activity-ref").val();
|
||||
const dxGrid = $("#dx-grid").val();
|
||||
const comment = $("#comment").val();
|
||||
const de = $("#de-call").val().toUpperCase();
|
||||
@@ -208,8 +208,8 @@ function addSpot() {
|
||||
spot["dx_call"] = dx;
|
||||
spot["freq"] = parseFloat(freqStr) * 1000;
|
||||
if (mode !== "") spot["mode"] = mode;
|
||||
if (sig !== "") spot["sig"] = sig;
|
||||
if (sigRef !== "") spot["sig_refs"] = [{sig: sig, id: sigRef}];
|
||||
if (activity !== "") spot["activity"] = activity;
|
||||
if (activityRef !== "") spot["activity_refs"] = [{activity: activity, id: activityRef}];
|
||||
if (dxGrid !== "") spot["dx_grid"] = dxGrid;
|
||||
if (comment !== "") spot["comment"] = comment;
|
||||
spot["de_call"] = de;
|
||||
@@ -232,7 +232,7 @@ function addSpot() {
|
||||
handling["upstream_credentials"] = loadCredentials(upstreamProviderName);
|
||||
}
|
||||
|
||||
$.ajax("/api/v2/spot", {
|
||||
$.ajax("/api/v3/spot", {
|
||||
data: JSON.stringify({spot, handling}),
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
@@ -291,7 +291,7 @@ $("#mode").change(function () {
|
||||
});
|
||||
|
||||
// Update upstream area and credentials button when activity changes
|
||||
$("#sig").change(function () {
|
||||
$("#activity").change(function () {
|
||||
updateUpstreamArea();
|
||||
});
|
||||
|
||||
|
||||
+14
-14
@@ -10,7 +10,7 @@ let alerts = [];
|
||||
// to alerts
|
||||
function loadAlerts() {
|
||||
$.ajax({
|
||||
url: '/api/v2/alerts' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
url: '/api/v3/alerts' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
// Store data
|
||||
@@ -24,7 +24,7 @@ function loadAlerts() {
|
||||
// Build a query string for the API, based on the filters that the user has selected.
|
||||
function buildQueryString() {
|
||||
let str = "?";
|
||||
["dx_continent", "source", "sig"].forEach(fn => {
|
||||
["dx_continent", "source", "activity"].forEach(fn => {
|
||||
if (!allFilterOptionsSelected(fn)) {
|
||||
str = str + getQueryStringFor(fn) + "&";
|
||||
}
|
||||
@@ -210,14 +210,14 @@ function addAlertRowsToTable(tbody, alerts) {
|
||||
if (a["dx_calls"] != null) {
|
||||
dx_calls_html = a["dx_calls"].map(call => `<a class='dx-link' href='https://qrz.com/db/${call}' target='_new'>${call}</a>`).join(", ");
|
||||
}
|
||||
if (dx_calls_html === "" && a["sig"] === "Contest") {
|
||||
if (dx_calls_html === "" && a["activity"] === "Contest") {
|
||||
// Contest = true and no DX callsigns, so display "Contest"
|
||||
dx_calls_html = "Contest"
|
||||
}
|
||||
|
||||
// Format DXpedition country
|
||||
let dx_country_html = "";
|
||||
if (a["sig"] === "DXpedition" && a["dx_country"] != null && a["dx_country"] !== "") {
|
||||
if (a["activity"] === "DXpedition" && a["dx_country"] != null && a["dx_country"] !== "") {
|
||||
dx_country_html = `<br/>${a["dx_country"]}`;
|
||||
}
|
||||
|
||||
@@ -252,23 +252,23 @@ function addAlertRowsToTable(tbody, alerts) {
|
||||
|
||||
// Activity or fallback to "General DX"
|
||||
let activityText = "General DX";
|
||||
if (a["sig"]) {
|
||||
activityText = a["sig"];
|
||||
if (a["activity"]) {
|
||||
activityText = a["activity"];
|
||||
}
|
||||
|
||||
// Format activity refs
|
||||
let activityRefs = "";
|
||||
if (a["sig_refs"] != null) {
|
||||
if (a["activity_refs"] != null) {
|
||||
const items = [];
|
||||
for (let i = 0; i < a["sig_refs"].length; i++) {
|
||||
if (a["sig_refs"][i]["url"] != null) {
|
||||
items[i] = `<a href='${encodeURI(a["sig_refs"][i]["url"])}' title='${escapeHtml(a["sig_refs"][i]["name"])}' target='_new' class='activity-ref-link'>${escapeHtml(a["sig_refs"][i]["id"])}</a>`
|
||||
for (let i = 0; i < a["activity_refs"].length; i++) {
|
||||
if (a["activity_refs"][i]["url"] != null) {
|
||||
items[i] = `<a href='${encodeURI(a["activity_refs"][i]["url"])}' title='${escapeHtml(a["activity_refs"][i]["name"])}' target='_new' class='activity-ref-link'>${escapeHtml(a["activity_refs"][i]["id"])}</a>`
|
||||
} else {
|
||||
items[i] = `${escapeHtml(a["sig_refs"][i]["id"])}`
|
||||
items[i] = `${escapeHtml(a["activity_refs"][i]["id"])}`
|
||||
}
|
||||
// If this is a satellite alert the ref will just be the satellite, but DX grid is also important, so
|
||||
// show that if we can.
|
||||
if (a["sig_refs"][i]["sig"] === "Satellite" && a["dx_grid"] != null) {
|
||||
if (a["activity_refs"][i]["activity"] === "Satellite" && a["dx_grid"] != null) {
|
||||
items[i] += " from " + a["dx_grid"];
|
||||
}
|
||||
}
|
||||
@@ -330,7 +330,7 @@ function addAlertRowsToTable(tbody, alerts) {
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query alerts.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v2/options', function (jsonData) {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
@@ -338,7 +338,7 @@ function loadOptions() {
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#source-options", "source", options["alert_providers"]);
|
||||
// Alerts can only ever be tagged with activities that have an alert source, so only offer those as filters here
|
||||
generateActivitiesMultiToggleFilterCard(options["sigs"].filter(o => o["alerts_possible"]), false);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"].filter(o => o["alerts_possible"]), false);
|
||||
|
||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||
|
||||
+5
-5
@@ -20,7 +20,7 @@ function loadSpots() {
|
||||
sseAbortController.abort();
|
||||
}
|
||||
$.ajax({
|
||||
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
url: '/api/v3/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update bands display
|
||||
@@ -39,7 +39,7 @@ function startSSEConnection() {
|
||||
sseAbortController = new AbortController();
|
||||
|
||||
// No need to include QRZ/HamQTH credentials because the information wouldn't be displayed on the bands panel anyway
|
||||
fetchEventSource('/api/v2/spots/stream' + buildQueryString(), {
|
||||
fetchEventSource('/api/v3/spots/stream' + buildQueryString(), {
|
||||
signal: sseAbortController.signal,
|
||||
openWhenHidden: true,
|
||||
|
||||
@@ -80,7 +80,7 @@ function expireOldSpots() {
|
||||
// in the bands page's version of this, because nothing QRZ.com/HamQTH can provide will affect the display.
|
||||
function buildQueryString() {
|
||||
let str = "?";
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "activity"].forEach(fn => {
|
||||
if (!allFilterOptionsSelected(fn)) {
|
||||
str = str + getQueryStringFor(fn) + "&";
|
||||
}
|
||||
@@ -281,7 +281,7 @@ function removeDuplicatesForBandPanel(spotList) {
|
||||
// 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/v2/options', function (jsonData) {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
@@ -295,7 +295,7 @@ function loadOptions() {
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["sigs"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"]);
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de_continent_options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
|
||||
+24
-9
@@ -33,6 +33,16 @@ function saveSettings() {
|
||||
// Load settings from local storage and set up the filter selectors. Suppressed if "use local storage" is false.
|
||||
function loadSettings() {
|
||||
if (useLocalStorage) {
|
||||
// Spothole v2 and earlier stored activity filter settings with "sig" in the element IDs, so migrate these to
|
||||
// their new names.
|
||||
Object.keys(localStorage).forEach(function (key) {
|
||||
if (key.startsWith("#filter-button-sig-")) {
|
||||
const newKey = key.replace("#filter-button-sig-", "#filter-button-activity-").replace("-NO_SIG:", "-NO_ACTIVITY:");
|
||||
localStorage.setItem(newKey, localStorage.getItem(key));
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
|
||||
// Find all local storage entries and push their data to the corresponding UI element
|
||||
Object.keys(localStorage).forEach(function (key) {
|
||||
if (key.startsWith("#") && key.includes(":")) {
|
||||
@@ -67,7 +77,12 @@ function loadURLParams() {
|
||||
updateSelectFromParam(params, "limit", "alerts_to_fetch"); // Only on Alerts page
|
||||
updateSelectFromParam(params, "max_age", "max_spot_age"); // Only on Map & Bands pages
|
||||
updateFilterFromParam(params, "band", "band");
|
||||
updateFilterFromParam(params, "sig", "sig");
|
||||
updateFilterFromParam(params, "activity", "activity");
|
||||
// Spothole v2 and earlier called the "activity" param "sig", so support this for existing embeds
|
||||
if (params.get("activity") == null && params.get("sig") != null) {
|
||||
params.set("activity", params.get("sig").replace("NO_SIG", "NO_ACTIVITY"));
|
||||
updateFilterFromParam(params, "activity", "activity");
|
||||
}
|
||||
updateFilterFromParam(params, "source", "source");
|
||||
updateFilterFromParam(params, "mode", "mode");
|
||||
updateFilterFromParam(params, "dx_continent", "dx_continent");
|
||||
@@ -156,41 +171,41 @@ function buildActivityFilterGrid(activity_options) {
|
||||
const $grid = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
activity_options.forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-activity storeable-checkbox" id="filter-button-activity-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-activity-${domSafeName}" for="filter-button-activity-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
});
|
||||
return $grid;
|
||||
}
|
||||
|
||||
// Generate activities filter card. This one is also a special case. includeGeneralDX controls whether the "General
|
||||
// DX" (NO_SIG) option is offered - this doesn't apply on the alerts page, where every alert has an activity.
|
||||
// DX" (NO_ACTIVITY) option is offered - this doesn't apply on the alerts page, where every alert has an activity.
|
||||
function generateActivitiesMultiToggleFilterCard(activity_options, includeGeneralDX = true) {
|
||||
const $list = $('<ul class="list-unstyled filter-section-list ps-0">');
|
||||
|
||||
const traditional = activity_options.filter(o => o["sig_type"] === "TRADITIONAL");
|
||||
const traditional = activity_options.filter(o => o["activity_type"] === "TRADITIONAL");
|
||||
appendActivityFilterSection($list, 'traditional', 'Traditional', true, includeGeneralDX || traditional.length > 0, $body => {
|
||||
if (includeGeneralDX) {
|
||||
$body.append(`<div class="w-100 mb-1"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-NO_SIG" value="NO_SIG" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-NO_SIG" for="filter-button-sig-NO_SIG"><i class="fa-solid fa-tower-cell"></i> General DX</label></div></div>`);
|
||||
$body.append(`<div class="w-100 mb-1"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-activity storeable-checkbox" id="filter-button-activity-NO_ACTIVITY" value="NO_ACTIVITY" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-activity-NO_ACTIVITY" for="filter-button-activity-NO_ACTIVITY"><i class="fa-solid fa-tower-cell"></i> General DX</label></div></div>`);
|
||||
}
|
||||
$body.append(buildActivityFilterGrid(traditional));
|
||||
});
|
||||
|
||||
const adventure = activity_options.filter(o => o["sig_type"] === "ADVENTURE");
|
||||
const adventure = activity_options.filter(o => o["activity_type"] === "ADVENTURE");
|
||||
appendActivityFilterSection($list, 'adventure', 'Adventure', true, adventure.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(adventure));
|
||||
});
|
||||
|
||||
const regional = activity_options.filter(o => o["sig_type"] === "REGIONAL");
|
||||
const regional = activity_options.filter(o => o["activity_type"] === "REGIONAL");
|
||||
appendActivityFilterSection($list, 'regional', 'Regional', false, regional.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(regional));
|
||||
});
|
||||
|
||||
const event = activity_options.filter(o => o["sig_type"] === "EVENT");
|
||||
const event = activity_options.filter(o => o["activity_type"] === "EVENT");
|
||||
appendActivityFilterSection($list, 'event', 'Event', false, event.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(event));
|
||||
});
|
||||
|
||||
$("#activity-options").append($list);
|
||||
$("#activity-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('sig', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('sig', false); return false;">None</a></div>`);
|
||||
$("#activity-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('activity', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('activity', false); return false;">None</a></div>`);
|
||||
}
|
||||
|
||||
// Method called when "All" or "None" is clicked
|
||||
|
||||
@@ -10,7 +10,7 @@ let ionosondeChart = null;
|
||||
|
||||
// Load solar conditions
|
||||
function loadSolarConditions() {
|
||||
$.getJSON('/api/v2/solar', function (jsonData) {
|
||||
$.getJSON('/api/v3/solar', function (jsonData) {
|
||||
|
||||
// HF
|
||||
|
||||
@@ -660,7 +660,7 @@ function dxStatsContientChanged() {
|
||||
|
||||
// Fetch DX stats from the API and render
|
||||
function loadDxStats() {
|
||||
$.getJSON('/api/v2/dxstats', function (jsonData) {
|
||||
$.getJSON('/api/v3/dxstats', function (jsonData) {
|
||||
dxStatsData = jsonData;
|
||||
renderDxStats();
|
||||
});
|
||||
|
||||
+13
-13
@@ -45,7 +45,7 @@ function loadSpots() {
|
||||
// 3) Subscribe to the SSE endpoint (with credentials if we have them) so that updates come with augmented
|
||||
// data if they can.
|
||||
$.ajax({
|
||||
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
url: '/api/v3/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update map
|
||||
@@ -58,7 +58,7 @@ function loadSpots() {
|
||||
// OK, we have credentials and have loaded once without them so the user has a basic map. Now reload
|
||||
// with the credentials and replace what's on the map, so we can improve the data.
|
||||
$.ajax({
|
||||
url: '/api/v2/spots' + buildQueryString(),
|
||||
url: '/api/v3/spots' + buildQueryString(),
|
||||
dataType: 'json',
|
||||
headers: getCredentialHeaders(),
|
||||
success: function (jsonData2) {
|
||||
@@ -85,7 +85,7 @@ function startSSEConnection() {
|
||||
sseAbortController = new AbortController();
|
||||
|
||||
// SSE is going to fetch only a few spots at a time, so now we include QRZ/HamQTH credentials because the delay won't be significant.
|
||||
fetchEventSource('/api/v2/spots/stream' + buildQueryString(), {
|
||||
fetchEventSource('/api/v3/spots/stream' + buildQueryString(), {
|
||||
headers: getCredentialHeaders(),
|
||||
signal: sseAbortController.signal,
|
||||
openWhenHidden: true,
|
||||
@@ -183,7 +183,7 @@ function removeSpotFromMap(key) {
|
||||
// Build a query string for the API, based on the filters that the user has selected.
|
||||
function buildQueryString() {
|
||||
let str = "?";
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "activity"].forEach(fn => {
|
||||
if (!allFilterOptionsSelected(fn)) {
|
||||
str = str + getQueryStringFor(fn) + "&";
|
||||
}
|
||||
@@ -276,19 +276,19 @@ function getTooltipText(s) {
|
||||
|
||||
// Activity or fallback to source
|
||||
let activitySourceText = s["source"];
|
||||
if (s["sig"]) {
|
||||
activitySourceText = s["sig"];
|
||||
if (s["activity"]) {
|
||||
activitySourceText = s["activity"];
|
||||
}
|
||||
|
||||
// Format activity refs
|
||||
let activityRefs = "";
|
||||
if (s["sig_refs"] != null) {
|
||||
if (s["activity_refs"] != null) {
|
||||
const items = [];
|
||||
for (let i = 0; i < s["sig_refs"].length; i++) {
|
||||
if (s["sig_refs"][i]["url"] != null) {
|
||||
items[i] = `<a href='${s["sig_refs"][i]["url"]}' title='${s["sig_refs"][i]["name"]}' target='_new' class='activity-ref-link'>${s["sig_refs"][i]["id"]}</a>`
|
||||
for (let i = 0; i < s["activity_refs"].length; i++) {
|
||||
if (s["activity_refs"][i]["url"] != null) {
|
||||
items[i] = `<a href='${s["activity_refs"][i]["url"]}' title='${s["activity_refs"][i]["name"]}' target='_new' class='activity-ref-link'>${s["activity_refs"][i]["id"]}</a>`
|
||||
} else {
|
||||
items[i] = `${s["sig_refs"][i]["id"]}`
|
||||
items[i] = `${s["activity_refs"][i]["id"]}`
|
||||
}
|
||||
}
|
||||
activityRefs = items.join(", ");
|
||||
@@ -325,7 +325,7 @@ function getTooltipText(s) {
|
||||
// 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/v2/options', function (jsonData) {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
@@ -339,7 +339,7 @@ function loadOptions() {
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["sigs"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"]);
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de_continent_options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
|
||||
+12
-12
@@ -20,7 +20,7 @@ function loadSpots() {
|
||||
|
||||
// Make the new query. No credential headers on the first load to keep things quick
|
||||
$.ajax({
|
||||
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
url: '/api/v3/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update table
|
||||
@@ -43,7 +43,7 @@ function startSSEConnection() {
|
||||
sseAbortController = new AbortController();
|
||||
|
||||
// SSE is going to fetch only a few spots at a time, so now we include QRZ/HamQTH credentials because the delay won't be significant.
|
||||
fetchEventSource('/api/v2/spots/stream' + buildQueryString(), {
|
||||
fetchEventSource('/api/v3/spots/stream' + buildQueryString(), {
|
||||
headers: getCredentialHeaders(),
|
||||
signal: sseAbortController.signal,
|
||||
openWhenHidden: true,
|
||||
@@ -100,7 +100,7 @@ function startSSEConnection() {
|
||||
// Build a query string for the API, based on the filters that the user has selected.
|
||||
function buildQueryString() {
|
||||
let str = "?";
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "activity"].forEach(fn => {
|
||||
if (!allFilterOptionsSelected(fn)) {
|
||||
str = str + getQueryStringFor(fn) + "&";
|
||||
}
|
||||
@@ -329,19 +329,19 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
|
||||
// Format activity
|
||||
let activityText = "General DX";
|
||||
if (s["sig"]) {
|
||||
activityText = s["sig"];
|
||||
if (s["activity"]) {
|
||||
activityText = s["activity"];
|
||||
}
|
||||
|
||||
// Format activity refs
|
||||
let activityRefs = "";
|
||||
if (s["sig_refs"] != null) {
|
||||
if (s["activity_refs"] != null) {
|
||||
const items = [];
|
||||
for (let i = 0; i < s["sig_refs"].length; i++) {
|
||||
if (s["sig_refs"][i]["url"] != null) {
|
||||
items[i] = `<span style="white-space: nowrap;"><a href='${encodeURI(s["sig_refs"][i]["url"])}' title='${escapeHtml(s["sig_refs"][i]["name"])}' target='_new' class='activity-ref-link'>${escapeHtml(s["sig_refs"][i]["id"])}</a></span>`
|
||||
for (let i = 0; i < s["activity_refs"].length; i++) {
|
||||
if (s["activity_refs"][i]["url"] != null) {
|
||||
items[i] = `<span style="white-space: nowrap;"><a href='${encodeURI(s["activity_refs"][i]["url"])}' title='${escapeHtml(s["activity_refs"][i]["name"])}' target='_new' class='activity-ref-link'>${escapeHtml(s["activity_refs"][i]["id"])}</a></span>`
|
||||
} else {
|
||||
items[i] = `<span style="white-space: nowrap;">${escapeHtml(s["sig_refs"][i]["id"])}</span>`
|
||||
items[i] = `<span style="white-space: nowrap;">${escapeHtml(s["activity_refs"][i]["id"])}</span>`
|
||||
}
|
||||
}
|
||||
activityRefs = items.join(", ");
|
||||
@@ -464,7 +464,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
// 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/v2/options', function (jsonData) {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
@@ -478,7 +478,7 @@ function loadOptions() {
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["sigs"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"]);
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de_continent_options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
// Load server status
|
||||
function loadStatus() {
|
||||
$.getJSON('/api/v2/status', function (jsonData) {
|
||||
$.getJSON('/api/v3/status', function (jsonData) {
|
||||
$("#software_version").text(jsonData["software_version"]);
|
||||
$("#server_owner_callsign").text(jsonData["server_owner_callsign"]);
|
||||
$("#up-since").text(moment().subtract(jsonData["uptime"], 'seconds').fromNow());
|
||||
@@ -54,10 +54,10 @@ function loadStatus() {
|
||||
</div>`);
|
||||
});
|
||||
|
||||
jsonData["sig_ref_data_providers"].forEach(p => {
|
||||
$("#sig_ref_data_providers-status-container").append(`
|
||||
jsonData["activity_ref_data_providers"].forEach(p => {
|
||||
$("#activity_ref_data_providers-status-container").append(`
|
||||
<div class="row row-cols-1 row-cols-md-4 g-4 mb-4 mb-md-2">
|
||||
<div class="col"><strong>${p["sig_name"]}</strong></div>
|
||||
<div class="col"><strong>${p["activity_name"]}</strong></div>
|
||||
<div class="col">Status: ${p["status"]}</div>
|
||||
<div class="col">Last updated: ${(p["enabled"] && p["last_updated"] > 0) ? moment.unix(p["last_updated"]).utc().fromNow() : "N/A"}</div>
|
||||
<div class="col">References: ${p["enabled"] ? p["reference_count"] : "N/A"}</div>
|
||||
|
||||
Reference in New Issue
Block a user