mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 09:09:30 -07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7900e59aab | ||
|
|
9b422dd697 | ||
|
|
e4ce0b6cc6 | ||
|
|
b0e5018179 | ||
|
|
6422589b5c | ||
|
|
407da90f8a | ||
|
|
3a0e6aa345 | ||
|
|
650dda7b61 | ||
|
|
8f1e8ffb74 | ||
|
|
93af84cbd8 | ||
|
|
117f66e9d0 | ||
|
|
bca9376edc |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,3 +12,4 @@ Please do not report issues with api.weather.gov being down. It's a new service
|
|||||||
Please include:
|
Please include:
|
||||||
* Web browser and OS
|
* Web browser and OS
|
||||||
* Headend Information text block from the very bottom of the web page
|
* Headend Information text block from the very bottom of the web page
|
||||||
|
* How you're running Weatherstar (Node, Dockerfile, Dockerfile.server, etc.)
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ server {
|
|||||||
|
|
||||||
add_header X-Weatherstar true always;
|
add_header X-Weatherstar true always;
|
||||||
|
|
||||||
|
include /etc/nginx/includes/wsqs_redirect.conf;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
index redirect.html index.html index.htm;
|
index index.html index.htm;
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.4",
|
"version": "6.2.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.4",
|
"version": "6.2.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.4",
|
"version": "6.2.7",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -106,17 +106,34 @@ const init = async () => {
|
|||||||
|
|
||||||
// attempt to parse the url parameters
|
// attempt to parse the url parameters
|
||||||
const parsedParameters = parseQueryString();
|
const parsedParameters = parseQueryString();
|
||||||
const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon;
|
const loadFromParsed = !!parsedParameters.latLon;
|
||||||
|
|
||||||
// Auto load the parsed parameters and fall back to the previous query
|
// Auto load the parsed parameters and fall back to the previous query
|
||||||
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
||||||
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
||||||
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
||||||
|
|
||||||
if (query && latLon && !fromGPS) {
|
if (parsedParameters.latLonQuery && !parsedParameters.latLon) {
|
||||||
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||||
txtAddress.value = query;
|
txtAddress.value = parsedParameters.latLonQuery;
|
||||||
loadData(JSON.parse(latLon));
|
const geometry = await geocodeLatLonQuery(parsedParameters.latLonQuery);
|
||||||
|
if (geometry) {
|
||||||
|
doRedirectToGeometry(geometry);
|
||||||
|
}
|
||||||
|
} else if (latLon && !fromGPS) {
|
||||||
|
// update in-page search box if using cached data, or parsed parameter
|
||||||
|
if ((query && !loadFromParsed) || (parsedParameters.latLonQuery && loadFromParsed)) {
|
||||||
|
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||||
|
txtAddress.value = query;
|
||||||
|
}
|
||||||
|
// use lat-long lookup if that's all that was provided in the query string
|
||||||
|
if (loadFromParsed && parsedParameters.latLon && !parsedParameters.latLonQuery) {
|
||||||
|
const { lat, lon } = JSON.parse(latLon);
|
||||||
|
getForecastFromLatLon(lat, lon, true);
|
||||||
|
} else {
|
||||||
|
// otherwise use pre-stored data
|
||||||
|
loadData(JSON.parse(latLon));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (fromGPS) {
|
if (fromGPS) {
|
||||||
btnGetGpsClick();
|
btnGetGpsClick();
|
||||||
@@ -162,6 +179,26 @@ const init = async () => {
|
|||||||
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const geocodeLatLonQuery = async (query) => {
|
||||||
|
try {
|
||||||
|
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||||
|
data: {
|
||||||
|
text: query,
|
||||||
|
f: 'json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const loc = data.locations?.[0];
|
||||||
|
if (loc) {
|
||||||
|
return loc.feature.geometry;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Geocoding failed:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const autocompleteOnSelect = async (suggestion) => {
|
const autocompleteOnSelect = async (suggestion) => {
|
||||||
// Note: it's fine that this uses json instead of safeJson since it's infrequent and user-initiated
|
// Note: it's fine that this uses json instead of safeJson since it's infrequent and user-initiated
|
||||||
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
this.setAutoReload();
|
this.setAutoReload();
|
||||||
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.data) resolve(this.data);
|
if (this.data) resolve({ data: this.data, parameters: this.weatherParameters });
|
||||||
// data not available, put it into the data callback queue
|
// data not available, put it into the data callback queue
|
||||||
this.getDataCallbacks.push(() => resolve(this.data));
|
this.getDataCallbacks.push(() => resolve(this.data));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const incrementInterval = (force) => {
|
|||||||
|
|
||||||
const drawScreen = async () => {
|
const drawScreen = async () => {
|
||||||
// get the conditions
|
// get the conditions
|
||||||
const data = await getCurrentWeather();
|
const { data, parameters } = await getCurrentWeather();
|
||||||
|
|
||||||
// create a data object (empty if no valid current weather conditions)
|
// create a data object (empty if no valid current weather conditions)
|
||||||
const scrollData = data || {};
|
const scrollData = data || {};
|
||||||
@@ -100,7 +100,7 @@ const drawScreen = async () => {
|
|||||||
// if we have no current weather and no hazards, there's nothing to display
|
// if we have no current weather and no hazards, there's nothing to display
|
||||||
if (!data && (!scrollData.hazards || scrollData.hazards.length === 0)) return;
|
if (!data && (!scrollData.hazards || scrollData.hazards.length === 0)) return;
|
||||||
|
|
||||||
const thisScreen = workingScreens[screenIndex](scrollData);
|
const thisScreen = workingScreens[screenIndex](scrollData, parameters);
|
||||||
|
|
||||||
// update classes on the scroll area
|
// update classes on the scroll area
|
||||||
mainScroll.classList.forEach((cls) => { if (cls !== 'scroll') mainScroll.classList.remove(cls); });
|
mainScroll.classList.forEach((cls) => { if (cls !== 'scroll') mainScroll.classList.remove(cls); });
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import STATUS from './status.mjs';
|
import STATUS from './status.mjs';
|
||||||
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
||||||
import { safeJson } from './utils/fetch.mjs';
|
import { safeJson } from './utils/fetch.mjs';
|
||||||
import { temperature as temperatureUnit, distanceKilometers } from './utils/units.mjs';
|
import { temperature as temperatureUnit, windSpeed as windUnit } from './utils/units.mjs';
|
||||||
import { getHourlyIcon } from './icons.mjs';
|
import { getHourlyIcon } from './icons.mjs';
|
||||||
import { directionToNSEW } from './utils/calc.mjs';
|
import { directionToNSEW } from './utils/calc.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
@@ -191,7 +191,7 @@ class Hourly extends WeatherDisplay {
|
|||||||
const parseForecast = async (data) => {
|
const parseForecast = async (data) => {
|
||||||
// get unit converters
|
// get unit converters
|
||||||
const temperatureConverter = temperatureUnit();
|
const temperatureConverter = temperatureUnit();
|
||||||
const distanceConverter = distanceKilometers();
|
const windConverter = windUnit();
|
||||||
|
|
||||||
// parse data
|
// parse data
|
||||||
const temperature = expand(data.temperature.values);
|
const temperature = expand(data.temperature.values);
|
||||||
@@ -210,8 +210,8 @@ const parseForecast = async (data) => {
|
|||||||
temperature: temperatureConverter(temperature[idx]),
|
temperature: temperatureConverter(temperature[idx]),
|
||||||
temperatureUnit: temperatureConverter.units,
|
temperatureUnit: temperatureConverter.units,
|
||||||
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
||||||
windSpeed: distanceConverter(windSpeed[idx]),
|
windSpeed: windConverter(windSpeed[idx]),
|
||||||
windUnit: distanceConverter.units,
|
windUnit: windConverter.units,
|
||||||
windDirection: directionToNSEW(windDirection[idx]),
|
windDirection: directionToNSEW(windDirection[idx]),
|
||||||
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
||||||
skyCover: skyCover[idx],
|
skyCover: skyCover[idx],
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const getWeather = async (latLon, haveDataCallback) => {
|
|||||||
weatherParameters.forecast = point.properties.forecast;
|
weatherParameters.forecast = point.properties.forecast;
|
||||||
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
||||||
weatherParameters.stations = stations.features;
|
weatherParameters.stations = stations.features;
|
||||||
|
weatherParameters.relativeLocation = point.properties.relativeLocation.properties;
|
||||||
|
|
||||||
// update the main process for display purposes
|
// update the main process for display purposes
|
||||||
populateWeatherParameters(weatherParameters, point.properties);
|
populateWeatherParameters(weatherParameters, point.properties);
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ url_encode() {
|
|||||||
|
|
||||||
# build query string from WSQS_ env vars
|
# build query string from WSQS_ env vars
|
||||||
while IFS='=' read -r key val; do
|
while IFS='=' read -r key val; do
|
||||||
|
# Skip empty lines
|
||||||
|
[ -z "$key" ] && continue
|
||||||
|
|
||||||
# Remove WSQS_ prefix and convert underscores to hyphens
|
# Remove WSQS_ prefix and convert underscores to hyphens
|
||||||
key="${key#WSQS_}"
|
key="${key#WSQS_}"
|
||||||
key="${key//_/-}"
|
key="${key//_/-}"
|
||||||
@@ -23,11 +26,16 @@ while IFS='=' read -r key val; do
|
|||||||
QS="${key}=${encoded_val}"
|
QS="${key}=${encoded_val}"
|
||||||
fi
|
fi
|
||||||
done << EOF
|
done << EOF
|
||||||
$(env | grep '^WSQS_')
|
$(env | grep '^WSQS_' || true)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
mkdir -p /etc/nginx/includes
|
||||||
|
|
||||||
if [ -n "$QS" ]; then
|
if [ -n "$QS" ]; then
|
||||||
|
# Escape the query string for use in JavaScript (escape backslashes and single quotes)
|
||||||
|
QS_ESCAPED=$(printf '%s' "$QS" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\'/g")
|
||||||
|
|
||||||
|
# Generate redirect.html with JavaScript logic
|
||||||
cat > "$ROOT/redirect.html" <<EOF
|
cat > "$ROOT/redirect.html" <<EOF
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -35,10 +43,36 @@ if [ -n "$QS" ]; then
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Redirecting</title>
|
<title>Redirecting</title>
|
||||||
<meta http-equiv="refresh" content="0;url=/index.html?$QS" />
|
<meta http-equiv="refresh" content="0;url=/index.html?$QS" />
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var wsqsParams = '$QS_ESCAPED';
|
||||||
|
var currentParams = window.location.search.substring(1);
|
||||||
|
var targetParams = currentParams || wsqsParams;
|
||||||
|
window.location.replace('/index.html?' + targetParams);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Generate nginx config for conditional redirects
|
||||||
|
cat > /etc/nginx/includes/wsqs_redirect.conf <<'EOF'
|
||||||
|
location = / {
|
||||||
|
if ($args = '') {
|
||||||
|
rewrite ^ /redirect.html last;
|
||||||
|
}
|
||||||
|
rewrite ^/$ /index.html?$args? redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
if ($args = '') {
|
||||||
|
rewrite ^ /redirect.html last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
touch /etc/nginx/includes/wsqs_redirect.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
Reference in New Issue
Block a user