mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-24 05:35:10 +00:00
IDE inspection fixes and global autoformat
This commit is contained in:
@@ -462,13 +462,16 @@ As well as being my work, I have also gratefully received feature patches from S
|
|||||||
The project contains GeoJSON files for CQ and ITU zones, in the `/datafiles/` directory. These are MIT-licenced and, to
|
The project contains GeoJSON files for CQ and ITU zones, in the `/datafiles/` directory. These are MIT-licenced and, to
|
||||||
my knowledge, created by HA8TKS for his CQ and ITU zone layers for Leaflet.
|
my knowledge, created by HA8TKS for his CQ and ITU zone layers for Leaflet.
|
||||||
|
|
||||||
The project contains a set of flag icons generated using the "Noto Color Emoji" font on a Debian system, in the `/webassets/img/flags/` directory.
|
The project contains a set of flag icons generated using the "Noto Color Emoji" font on a Debian system, in the
|
||||||
|
`/webassets/img/flags/` directory.
|
||||||
|
|
||||||
The software uses a number of Python libraries as listed in `requirements.txt`, and a number of JavaScript libraries. This project would not have been possible without these libraries, so many thanks to their developers.
|
The software uses a number of Python libraries as listed in `requirements.txt`, and a number of JavaScript libraries.
|
||||||
|
This project would not have been possible without these libraries, so many thanks to their developers.
|
||||||
|
|
||||||
### Third Party Libraries
|
### Third Party Libraries
|
||||||
|
|
||||||
A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to their own licences and are not covered by the overall licence declared in the `LICENSE` file.
|
A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to
|
||||||
|
their own licences and are not covered by the overall licence declared in the `LICENSE` file.
|
||||||
|
|
||||||
A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to
|
A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to
|
||||||
their own licences and are not covered by the overall licence declared in the `LICENSE` file.
|
their own licences and are not covered by the overall licence declared in the `LICENSE` file.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from core.sig_utils import get_ref_regex_for_sig
|
|||||||
from core.utils import serialize_everything
|
from core.utils import serialize_everything
|
||||||
from data.sig_ref import SIGRef
|
from data.sig_ref import SIGRef
|
||||||
from data.spot import Spot
|
from data.spot import Spot
|
||||||
|
from spotproviders.spot_provider import SpotProvider
|
||||||
|
|
||||||
RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
|
RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
|||||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||||
self._spots = None
|
self._spots = None
|
||||||
self._web_server_metrics = None
|
self._web_server_metrics = None
|
||||||
|
self._spot_providers = None
|
||||||
super().__init__(application, request, **kwargs)
|
super().__init__(application, request, **kwargs)
|
||||||
|
|
||||||
def initialize(self, spots, web_server_metrics, spot_providers=None):
|
def initialize(self, spots, web_server_metrics, spot_providers=None):
|
||||||
@@ -185,7 +187,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
|||||||
# Submit spot to the upstream provider
|
# Submit spot to the upstream provider
|
||||||
provider.submit_spot(spot, upstream_credentials)
|
provider.submit_spot(spot, upstream_credentials)
|
||||||
# Trigger a re-poll after 1 second so the spot appears quickly
|
# Trigger a re-poll after 1 second so the spot appears quickly
|
||||||
threading.Timer(1.0, lambda: provider.force_poll()).start()
|
threading.Timer(1.0, provider.force_poll).start()
|
||||||
except NotImplementedError as e:
|
except NotImplementedError as e:
|
||||||
upstream_warning = str(e)
|
upstream_warning = str(e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -218,7 +220,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
|||||||
self.set_header("Cache-Control", "no-store")
|
self.set_header("Cache-Control", "no-store")
|
||||||
self.set_header("Content-Type", "application/json")
|
self.set_header("Content-Type", "application/json")
|
||||||
|
|
||||||
def _find_provider(self, provider_name, sig):
|
def _find_provider(self, provider_name, sig) -> SpotProvider | None:
|
||||||
"""Find an enabled provider by name that can submit spots for the given SIG."""
|
"""Find an enabled provider by name that can submit spots for the given SIG."""
|
||||||
|
|
||||||
for p in self._spot_providers:
|
for p in self._spot_providers:
|
||||||
@@ -226,7 +228,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
|||||||
return p
|
return p
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _verify_recaptcha(self, token):
|
@staticmethod
|
||||||
|
def _verify_recaptcha(token):
|
||||||
"""Verify a Google reCAPTCHA v2 token. Returns True if valid."""
|
"""Verify a Google reCAPTCHA v2 token. Returns True if valid."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
|||||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||||
self._status_data = None
|
self._status_data = None
|
||||||
self._web_server_metrics = None
|
self._web_server_metrics = None
|
||||||
|
self._spot_providers = None
|
||||||
super().__init__(application, request, **kwargs)
|
super().__init__(application, request, **kwargs)
|
||||||
|
|
||||||
def initialize(self, status_data, web_server_metrics, spot_providers=None):
|
def initialize(self, status_data, web_server_metrics, spot_providers=None):
|
||||||
@@ -42,23 +43,27 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
|||||||
if provider.can_submit_spot(sig.name):
|
if provider.can_submit_spot(sig.name):
|
||||||
spot_submit_providers.setdefault(sig.name, []).append(provider.name)
|
spot_submit_providers.setdefault(sig.name, []).append(provider.name)
|
||||||
|
|
||||||
|
# Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle
|
||||||
|
# things that aren't even available.
|
||||||
|
spot_sources: list = list(
|
||||||
|
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["spot_providers"])))
|
||||||
|
alert_sources = list(
|
||||||
|
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["alert_providers"])))
|
||||||
|
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
|
||||||
|
# one of our providers.
|
||||||
|
if ALLOW_SPOTTING:
|
||||||
|
spot_sources.append("API")
|
||||||
|
|
||||||
options = {"bands": BANDS,
|
options = {"bands": BANDS,
|
||||||
"modes": ALL_MODES,
|
"modes": ALL_MODES,
|
||||||
"mode_types": MODE_TYPES,
|
"mode_types": MODE_TYPES,
|
||||||
"sigs": SIGS,
|
"sigs": SIGS,
|
||||||
# Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle things that aren't even available.
|
"spot_sources": spot_sources,
|
||||||
"spot_sources": list(
|
"alert_sources": alert_sources,
|
||||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["spot_providers"]))),
|
|
||||||
"alert_sources": list(
|
|
||||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["alert_providers"]))),
|
|
||||||
"continents": CONTINENTS,
|
"continents": CONTINENTS,
|
||||||
"max_spot_age": MAX_SPOT_AGE,
|
"max_spot_age": MAX_SPOT_AGE,
|
||||||
"spot_allowed": ALLOW_SPOTTING,
|
"spot_allowed": ALLOW_SPOTTING,
|
||||||
"spot_submit_providers": spot_submit_providers}
|
"spot_submit_providers": spot_submit_providers}
|
||||||
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
|
|
||||||
# one of our proviers.
|
|
||||||
if ALLOW_SPOTTING:
|
|
||||||
options["spot_sources"].append("API")
|
|
||||||
|
|
||||||
self.write(json.dumps(options, default=serialize_everything))
|
self.write(json.dumps(options, default=serialize_everything))
|
||||||
self.set_status(200)
|
self.set_status(200)
|
||||||
|
|||||||
@@ -72,12 +72,14 @@ class WebServer:
|
|||||||
{"sse_alert_queues": self._sse_alert_queues, **handler_opts}),
|
{"sse_alert_queues": self._sse_alert_queues, **handler_opts}),
|
||||||
(r"/api/v1/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}),
|
(r"/api/v1/solar", APISolarConditionsHandler, {"solar_conditions": self._solar_conditions, **handler_opts}),
|
||||||
(r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}),
|
(r"/api/v1/dxstats", APIDxStatsHandler, {"spots": self._spots, **handler_opts}),
|
||||||
(r"/api/v1/options", APIOptionsHandler, {"status_data": self._status_data, "spot_providers": self._spot_providers, **handler_opts}),
|
(r"/api/v1/options", APIOptionsHandler,
|
||||||
|
{"status_data": self._status_data, "spot_providers": self._spot_providers, **handler_opts}),
|
||||||
(r"/api/v1/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}),
|
(r"/api/v1/status", APIStatusHandler, {"status_data": self._status_data, **handler_opts}),
|
||||||
(r"/api/v1/lookup/call", APILookupCallHandler, {**handler_opts}),
|
(r"/api/v1/lookup/call", APILookupCallHandler, {**handler_opts}),
|
||||||
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
|
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
|
||||||
(r"/api/v1/lookup/grid", APILookupGridHandler, {**handler_opts}),
|
(r"/api/v1/lookup/grid", APILookupGridHandler, {**handler_opts}),
|
||||||
(r"/api/v1/spot", APISpotHandler, {"spots": self._spots, "spot_providers": self._spot_providers, **handler_opts}),
|
(r"/api/v1/spot", APISpotHandler,
|
||||||
|
{"spots": self._spots, "spot_providers": self._spot_providers, **handler_opts}),
|
||||||
]
|
]
|
||||||
|
|
||||||
# If in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes
|
# If in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ class ParksNPeaks(HTTPSpotProvider):
|
|||||||
user_id = credentials.get("user_id", "")
|
user_id = credentials.get("user_id", "")
|
||||||
api_key = credentials.get("api_key", "")
|
api_key = credentials.get("api_key", "")
|
||||||
if not user_id or not api_key:
|
if not user_id or not api_key:
|
||||||
raise ValueError("Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account.")
|
raise ValueError(
|
||||||
|
"Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account.")
|
||||||
sig_ref = spot.sig_refs[0].id if spot.sig_refs else ""
|
sig_ref = spot.sig_refs[0].id if spot.sig_refs else ""
|
||||||
body = {
|
body = {
|
||||||
"actClass": spot.sig or "",
|
"actClass": spot.sig or "",
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ class SOTA(HTTPSpotProvider):
|
|||||||
"comments": spot.comment or "",
|
"comments": spot.comment or "",
|
||||||
"type": "TEST" # todo replatce with NORMAL/QRT once testing complete
|
"type": "TEST" # todo replatce with NORMAL/QRT once testing complete
|
||||||
}
|
}
|
||||||
headers = {**HTTP_HEADERS, "Authorization": "bearer " + access_token, "id_token": id_token, "Content-Type": "application/json"}
|
headers = {**HTTP_HEADERS, "Authorization": "bearer " + access_token, "id_token": id_token,
|
||||||
|
"Content-Type": "application/json"}
|
||||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
raise RuntimeError("SOTA API returned " + str(response.status_code) + ": " + response.text)
|
raise RuntimeError("SOTA API returned " + str(response.status_code) + ": " + response.text)
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class Tiles(HTTPSpotProvider):
|
|||||||
POLL_INTERVAL_SEC = 120
|
POLL_INTERVAL_SEC = 120
|
||||||
SPOTS_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/spots?active_hours=24"
|
SPOTS_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/spots?active_hours=24"
|
||||||
SUBMIT_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/self-spot"
|
SUBMIT_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/self-spot"
|
||||||
VALID_MODES = ["SSB", "CW", "FT8", "FT4", "FM", "DMR", "D-STAR", "M17", "AX.25", "JS8Call", "PSK31", "Olivia", "VarAC", "Other"]
|
VALID_MODES = ["SSB", "CW", "FT8", "FT4", "FM", "DMR", "D-STAR", "M17", "AX.25", "JS8Call", "PSK31", "Olivia",
|
||||||
|
"VarAC", "Other"]
|
||||||
|
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
super().__init__(provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
|
super().__init__(provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
|
||||||
@@ -82,11 +83,13 @@ class Tiles(HTTPSpotProvider):
|
|||||||
headers = {**HTTP_HEADERS, "Content-Type": "application/json"}
|
headers = {**HTTP_HEADERS, "Content-Type": "application/json"}
|
||||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
raise RuntimeError("Tiles on the Air API returned " + str(response.status_code) + ": " + response.text)
|
raise RuntimeError(
|
||||||
|
"Tiles on the Air API returned " + str(response.status_code) + ": " + response.text)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("The Tiles on the Air API requires a mode to be set.")
|
raise RuntimeError("The Tiles on the Air API requires a mode to be set.")
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("The Tiles on the Air API only supports self-spots, the DX call and spotter call must match.")
|
raise RuntimeError(
|
||||||
|
"The Tiles on the Air API only supports self-spots, the DX call and spotter call must match.")
|
||||||
|
|
||||||
|
|
||||||
# Utility function to keep the first decimal point in a given string but remove any others. Used to parse Tiles'
|
# Utility function to keep the first decimal point in a given string but remove any others. Used to parse Tiles'
|
||||||
|
|||||||
@@ -1696,8 +1696,8 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
example:
|
example:
|
||||||
POTA: [POTA]
|
POTA: [ POTA ]
|
||||||
SOTA: [SOTA]
|
SOTA: [ SOTA, GMA, ParksNPeaks ]
|
||||||
|
|
||||||
CallLookup:
|
CallLookup:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
// Credentials schema per provider name. Defines the fields to collect and how to label them.
|
// Credentials schema per provider name. Defines the fields to collect and how to label them.
|
||||||
var PROVIDER_CREDENTIAL_SCHEMAS = {
|
const PROVIDER_CREDENTIAL_SCHEMAS = {
|
||||||
// todo Figure out SOTA authentication
|
// todo Figure out SOTA authentication
|
||||||
// see e.g. https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAAccount.jsx
|
// see e.g. https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAAccount.jsx
|
||||||
// https://github.com/ham2k/app-polo/blob/main/src/store/apis/apiSOTA/apiSOTA.js
|
// https://github.com/ham2k/app-polo/blob/main/src/store/apis/apiSOTA/apiSOTA.js
|
||||||
// Refresh token? Way to show user that they need to log in again because cached credentials aren't valid?
|
// Refresh token? Way to show user that they need to log in again because cached credentials aren't valid?
|
||||||
// todo type: text/password distinction on text boxes so API keys can be obscured
|
// todo type: text/password distinction on text boxes so API keys can be obscured
|
||||||
"SOTA": [
|
"SOTA": [
|
||||||
{ key: "access_token", label: "SOTA Access Token", help: "" },
|
{key: "access_token", label: "SOTA Access Token", help: ""},
|
||||||
{ key: "id_token", label: "SOTA ID Token", help: "TODO SOTA authentication to provide this..." }
|
{key: "id_token", label: "SOTA ID Token", help: "TODO SOTA authentication to provide this..."}
|
||||||
],
|
],
|
||||||
"ParksNPeaks": [
|
"ParksNPeaks": [
|
||||||
{ key: "user_id", label: "Parks N Peaks User ID", help: "" },
|
{key: "user_id", label: "Parks N Peaks User ID", help: ""},
|
||||||
{ key: "api_key", label: "Parks N Peaks API Key", help: "Get your API key from your Parks N Peaks account." }
|
{key: "api_key", label: "Parks N Peaks API Key", help: "Get your API key from your Parks N Peaks account."}
|
||||||
],
|
],
|
||||||
"ZLOTA": [
|
"ZLOTA": [
|
||||||
{ key: "user_id", label: "ZLOTA User ID", help: "" },
|
{key: "user_id", label: "ZLOTA User ID", help: ""},
|
||||||
{ key: "api_key", label: "ZLOTA User PIN", help: "Get your PIN from your ZLOTA account." }
|
{key: "api_key", label: "ZLOTA User PIN", help: "Get your PIN from your ZLOTA account."}
|
||||||
],
|
],
|
||||||
"Tiles": [
|
"Tiles": [
|
||||||
{ key: "offline_spot_gateway_pin", label: "Offline Spot Gateway PIN", help: "Get your PIN from your Tiles on the Air account profile." }
|
{
|
||||||
|
key: "offline_spot_gateway_pin",
|
||||||
|
label: "Offline Spot Gateway PIN",
|
||||||
|
help: "Get your PIN from your Tiles on the Air account profile."
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +66,7 @@ function loadOptions() {
|
|||||||
function loadRecaptcha(siteKey) {
|
function loadRecaptcha(siteKey) {
|
||||||
window._recaptchaSiteKey = siteKey;
|
window._recaptchaSiteKey = siteKey;
|
||||||
if (!document.getElementById('recaptcha-script')) {
|
if (!document.getElementById('recaptcha-script')) {
|
||||||
var script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.id = 'recaptcha-script';
|
script.id = 'recaptcha-script';
|
||||||
script.src = 'https://www.google.com/recaptcha/api.js?render=explicit&onload=renderRecaptcha';
|
script.src = 'https://www.google.com/recaptcha/api.js?render=explicit&onload=renderRecaptcha';
|
||||||
script.async = true;
|
script.async = true;
|
||||||
@@ -87,8 +91,8 @@ function updateUpstreamArea() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sig = $("#sig").val();
|
const sig = $("#sig").val();
|
||||||
var providers = (sig && options["spot_submit_providers"][sig]) ? options["spot_submit_providers"][sig] : [];
|
const providers = (sig && options["spot_submit_providers"][sig]) ? options["spot_submit_providers"][sig] : [];
|
||||||
|
|
||||||
if (providers.length === 0) {
|
if (providers.length === 0) {
|
||||||
$("#upstream-area").hide();
|
$("#upstream-area").hide();
|
||||||
@@ -99,8 +103,8 @@ function updateUpstreamArea() {
|
|||||||
|
|
||||||
// Update the provider selector
|
// Update the provider selector
|
||||||
$("#upstream-provider-select").empty();
|
$("#upstream-provider-select").empty();
|
||||||
$.each(providers, function(i, name) {
|
$.each(providers, function (i, name) {
|
||||||
$("#upstream-provider-select").append($('<option>', { value: name, text: name }));
|
$("#upstream-provider-select").append($('<option>', {value: name, text: name}));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (providers.length > 1) {
|
if (providers.length > 1) {
|
||||||
@@ -117,7 +121,7 @@ function updateUpstreamArea() {
|
|||||||
|
|
||||||
// Update the credentials button visibility based on selected provider
|
// Update the credentials button visibility based on selected provider
|
||||||
function updateCredentialsButton() {
|
function updateCredentialsButton() {
|
||||||
var providerName = getSelectedUpstreamProvider();
|
const providerName = getSelectedUpstreamProvider();
|
||||||
if (providerName && PROVIDER_CREDENTIAL_SCHEMAS[providerName]) {
|
if (providerName && PROVIDER_CREDENTIAL_SCHEMAS[providerName]) {
|
||||||
$("#upstream-credentials-btn").show();
|
$("#upstream-credentials-btn").show();
|
||||||
} else {
|
} else {
|
||||||
@@ -127,7 +131,7 @@ function updateCredentialsButton() {
|
|||||||
|
|
||||||
// Get the currently selected upstream provider name
|
// Get the currently selected upstream provider name
|
||||||
function getSelectedUpstreamProvider() {
|
function getSelectedUpstreamProvider() {
|
||||||
var providers = (options && options["spot_submit_providers"] && $("#sig").val())
|
const providers = (options && options["spot_submit_providers"] && $("#sig").val())
|
||||||
? (options["spot_submit_providers"][$("#sig").val()] || [])
|
? (options["spot_submit_providers"][$("#sig").val()] || [])
|
||||||
: [];
|
: [];
|
||||||
if (providers.length === 0) return null;
|
if (providers.length === 0) return null;
|
||||||
@@ -137,18 +141,18 @@ function getSelectedUpstreamProvider() {
|
|||||||
|
|
||||||
// Show the credentials modal for the currently selected upstream provider
|
// Show the credentials modal for the currently selected upstream provider
|
||||||
function showCredentialsModal() {
|
function showCredentialsModal() {
|
||||||
var providerName = getSelectedUpstreamProvider();
|
const providerName = getSelectedUpstreamProvider();
|
||||||
if (!providerName || !PROVIDER_CREDENTIAL_SCHEMAS[providerName]) return;
|
if (!providerName || !PROVIDER_CREDENTIAL_SCHEMAS[providerName]) return;
|
||||||
|
|
||||||
var schema = PROVIDER_CREDENTIAL_SCHEMAS[providerName];
|
const schema = PROVIDER_CREDENTIAL_SCHEMAS[providerName];
|
||||||
var stored = loadCredentials(providerName);
|
const stored = loadCredentials(providerName);
|
||||||
|
|
||||||
$("#credentials-provider-name").text(providerName);
|
$("#credentials-provider-name").text(providerName);
|
||||||
$("#credentials-fields").empty();
|
$("#credentials-fields").empty();
|
||||||
|
|
||||||
$.each(schema, function(i, field) {
|
$.each(schema, function (i, field) {
|
||||||
var val = stored[field.key] || "";
|
const val = stored[field.key] || "";
|
||||||
var html = '<div class="mb-3">';
|
let html = '<div class="mb-3">';
|
||||||
html += '<label for="cred-' + field.key + '" class="form-label">' + field.label + '</label>';
|
html += '<label for="cred-' + field.key + '" class="form-label">' + field.label + '</label>';
|
||||||
html += '<input type="text" class="form-control" id="cred-' + field.key + '" value="' + $('<div>').text(val).html() + '">';
|
html += '<input type="text" class="form-control" id="cred-' + field.key + '" value="' + $('<div>').text(val).html() + '">';
|
||||||
if (field.help) {
|
if (field.help) {
|
||||||
@@ -165,12 +169,12 @@ function showCredentialsModal() {
|
|||||||
|
|
||||||
// Save credentials from the modal to local storage
|
// Save credentials from the modal to local storage
|
||||||
function saveCredentials() {
|
function saveCredentials() {
|
||||||
var providerName = $("#credentials-modal").data("provider");
|
const providerName = $("#credentials-modal").data("provider");
|
||||||
if (!providerName || !PROVIDER_CREDENTIAL_SCHEMAS[providerName]) return;
|
if (!providerName || !PROVIDER_CREDENTIAL_SCHEMAS[providerName]) return;
|
||||||
|
|
||||||
var schema = PROVIDER_CREDENTIAL_SCHEMAS[providerName];
|
const schema = PROVIDER_CREDENTIAL_SCHEMAS[providerName];
|
||||||
var creds = {};
|
const creds = {};
|
||||||
$.each(schema, function(i, field) {
|
$.each(schema, function (i, field) {
|
||||||
creds[field.key] = $("#cred-" + field.key).val();
|
creds[field.key] = $("#cred-" + field.key).val();
|
||||||
});
|
});
|
||||||
localStorage.setItem("upstream-credentials-" + providerName, JSON.stringify(creds));
|
localStorage.setItem("upstream-credentials-" + providerName, JSON.stringify(creds));
|
||||||
@@ -179,7 +183,7 @@ function saveCredentials() {
|
|||||||
|
|
||||||
// Load credentials for a provider from local storage
|
// Load credentials for a provider from local storage
|
||||||
function loadCredentials(providerName) {
|
function loadCredentials(providerName) {
|
||||||
var stored = localStorage.getItem("upstream-credentials-" + providerName);
|
const stored = localStorage.getItem("upstream-credentials-" + providerName);
|
||||||
return stored ? JSON.parse(stored) : {};
|
return stored ? JSON.parse(stored) : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,8 +241,8 @@ function addSpot() {
|
|||||||
spot["time"] = moment.utc().valueOf() / 1000.0;
|
spot["time"] = moment.utc().valueOf() / 1000.0;
|
||||||
|
|
||||||
// Upstream submission
|
// Upstream submission
|
||||||
var submitUpstream = $("#submit-upstream").is(":checked");
|
const submitUpstream = $("#submit-upstream").is(":checked");
|
||||||
var upstreamProviderName = getSelectedUpstreamProvider();
|
const upstreamProviderName = getSelectedUpstreamProvider();
|
||||||
if (submitUpstream && upstreamProviderName) {
|
if (submitUpstream && upstreamProviderName) {
|
||||||
if (!sig) {
|
if (!sig) {
|
||||||
showAddSpotError("A SIG must be selected to submit upstream.");
|
showAddSpotError("A SIG must be selected to submit upstream.");
|
||||||
@@ -257,14 +261,14 @@ function addSpot() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var creds = loadCredentials(upstreamProviderName);
|
const creds = loadCredentials(upstreamProviderName);
|
||||||
spot["submit_upstream"] = true;
|
spot["submit_upstream"] = true;
|
||||||
spot["upstream_provider"] = upstreamProviderName;
|
spot["upstream_provider"] = upstreamProviderName;
|
||||||
spot["upstream_credentials"] = creds;
|
spot["upstream_credentials"] = creds;
|
||||||
|
|
||||||
// Add CAPTCHA token if reCAPTCHA is loaded
|
// Add CAPTCHA token if reCAPTCHA is loaded
|
||||||
if (window._recaptchaWidgetId !== undefined) {
|
if (window._recaptchaWidgetId !== undefined) {
|
||||||
var token = grecaptcha.getResponse(window._recaptchaWidgetId);
|
const token = grecaptcha.getResponse(window._recaptchaWidgetId);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
showAddSpotError("Please complete the CAPTCHA to submit upstream.");
|
showAddSpotError("Please complete the CAPTCHA to submit upstream.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -539,7 +539,7 @@ function renderIonosondeData() {
|
|||||||
ctx.strokeStyle = gridColor;
|
ctx.strokeStyle = gridColor;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
// Add an extra vertical line for 30MHz, which should correspond to the top of the chart and avoid having
|
// Add an extra horizontal line for 30MHz, which should correspond to the top of the chart and avoid having
|
||||||
// no top "border" gridline
|
// no top "border" gridline
|
||||||
const y30 = scales.y.getPixelForValue(30);
|
const y30 = scales.y.getPixelForValue(30);
|
||||||
if (y30 >= chartArea.top && y30 <= chartArea.bottom) {
|
if (y30 >= chartArea.top && y30 <= chartArea.bottom) {
|
||||||
|
|||||||
Reference in New Issue
Block a user