diff --git a/server/routes/space-weather.js b/server/routes/space-weather.js index aa4526fc..d1126d93 100644 --- a/server/routes/space-weather.js +++ b/server/routes/space-weather.js @@ -210,15 +210,20 @@ module.exports = function (app, ctx) { } // Kp forecast — same format change; forecast uses lowercase 'kp' field. + // The endpoint mixes past observations with future predictions; keep only + // entries whose time_tag is in the future so the chart shows predictions. if (kForecastRes.status === 'fulfilled' && kForecastRes.value.ok) { const data = await kForecastRes.value.json(); if (data?.length) { const isObj = !Array.isArray(data[0]); const rows = isObj ? data : data.slice(1); - result.kp.forecast = rows.map((d) => ({ - time: isObj ? d.time_tag : d[0], - value: Number.isFinite(isObj ? d.kp : parseFloat(d[1])) ? (isObj ? d.kp : parseFloat(d[1])) : 0, - })); + const nowIso = new Date().toISOString(); + result.kp.forecast = rows + .filter((d) => (isObj ? d.time_tag : d[0]) > nowIso) + .map((d) => ({ + time: isObj ? d.time_tag : d[0], + value: Number.isFinite(isObj ? d.kp : parseFloat(d[1])) ? (isObj ? d.kp : parseFloat(d[1])) : 0, + })); } } diff --git a/src/components/PropagationPanel.jsx b/src/components/PropagationPanel.jsx index dc3b8457..50c7b84a 100644 --- a/src/components/PropagationPanel.jsx +++ b/src/components/PropagationPanel.jsx @@ -440,7 +440,8 @@ export const PropagationPanel = ({ {/* Geomag + Signal Noise + Source */}
- SFI {solarData?.sfi} • K {solarData?.kIndex} + SFI {bandConditions?.extras?.solarFlux ?? solarData?.sfi} • K{' '} + {bandConditions?.extras?.kIndex ?? solarData?.kIndex} {bandConditions?.extras?.geomagField && ( diff --git a/src/hooks/useBandConditions.js b/src/hooks/useBandConditions.js index 2ba76cec..e2a3a615 100644 --- a/src/hooks/useBandConditions.js +++ b/src/hooks/useBandConditions.js @@ -79,6 +79,8 @@ export const useBandConditions = () => { // Extra solar/geomag data from N0NBH setExtras({ + solarFlux: n0nbh.solarData?.solarFlux, + kIndex: n0nbh.solarData?.kIndex, aIndex: n0nbh.solarData?.aIndex, xray: n0nbh.solarData?.xray, solarWind: n0nbh.solarData?.solarWind, diff --git a/src/plugins/layers/useLightning.js b/src/plugins/layers/useLightning.js index 7f4c3c6a..4137fb3d 100644 --- a/src/plugins/layers/useLightning.js +++ b/src/plugins/layers/useLightning.js @@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { addMinimizeToggle } from './addMinimizeToggle.js'; import { makeDraggable } from './makeDraggable.js'; +import { getAlertSettings, playTone } from '../../utils/audioAlerts'; // Lightning Detection Plugin - Real-time lightning strike visualization // Data source: Blitzortung.org WebSocket API @@ -616,14 +617,12 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null, lowMemory panel.style.boxShadow = '0 0 20px rgba(255, 0, 0, 0.8)'; panel.style.transition = 'all 0.3s ease'; - // Play alert sound if available - try { - const audio = new Audio( - 'data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBTGH0fPTgjMGHm7A7+OZRAEKT6Ln77BcGAU+ltryxnMnBSp+y/HajDkHGWi77eWdTQ0MUKfj8LZjHAY4kdfy', - ); - audio.volume = 0.3; - audio.play().catch(() => {}); // Ignore errors if audio fails - } catch (e) {} + // Play alert tone via audio alerts system (if enabled in settings) + const alertSettings = getAlertSettings(); + const lightningConf = alertSettings.lightning; + if (lightningConf?.enabled) { + playTone(lightningConf.tone, alertSettings.volume ?? 0.5); + } } else { // No nearby strikes - restore normal appearance panel.style.border = '1px solid var(--border-color)'; diff --git a/src/utils/audioAlerts.js b/src/utils/audioAlerts.js index 653204d5..2a53d0d9 100644 --- a/src/utils/audioAlerts.js +++ b/src/utils/audioAlerts.js @@ -37,6 +37,7 @@ export const ALERT_FEEDS = { dxcluster: { label: 'DX Cluster', defaultTone: 'beep' }, dxpeditions: { label: 'DXpeditions', defaultTone: 'two-tone' }, contests: { label: 'Contests', defaultTone: 'simple' }, + lightning: { label: 'Lightning Proximity', defaultTone: 'chirp' }, }; const LS_KEY = 'ohc_audio_alerts';