Merge branch 'accius:main' into main

pull/106/head
trancen 2 months ago committed by GitHub
commit 144e7959a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -67,6 +67,13 @@ LAYOUT=modern
# Only uncomment if you have your own proxy running # Only uncomment if you have your own proxy running
# DXSPIDER_PROXY_URL=https://your-dxspider-proxy.com # DXSPIDER_PROXY_URL=https://your-dxspider-proxy.com
# DX Cluster source: auto | proxy | hamqth | dxspider
# auto = tries proxy first, then HamQTH, then direct telnet
# proxy = use DX Spider Proxy (set DXSPIDER_PROXY_URL above)
# hamqth = HamQTH CSV feed (HTTP, works everywhere)
# dxspider = direct telnet to DX Spider nodes (works locally/Pi)
# DX_CLUSTER_SOURCE=auto
# OpenWeatherMap API key (for local weather display) # OpenWeatherMap API key (for local weather display)
# Get a free key at https://openweathermap.org/api # Get a free key at https://openweathermap.org/api
# OPENWEATHER_API_KEY=your_api_key_here # OPENWEATHER_API_KEY=your_api_key_here

@ -225,6 +225,9 @@ else
CHROME_CMD="chromium-browser" CHROME_CMD="chromium-browser"
fi fi
# Trap Ctrl+Q to exit kiosk cleanly
trap 'pkill -f "chromium.*kiosk"; exit 0' SIGTERM SIGINT
$CHROME_CMD \ $CHROME_CMD \
--kiosk \ --kiosk \
--noerrdialogs \ --noerrdialogs \
@ -237,7 +240,17 @@ $CHROME_CMD \
--overscroll-history-navigation=0 \ --overscroll-history-navigation=0 \
--disable-pinch \ --disable-pinch \
--incognito \ --incognito \
http://localhost:3000 http://localhost:3000 &
CHROME_PID=$!
echo "OpenHamClock kiosk running (PID: $CHROME_PID)"
echo "Exit methods:"
echo " - Alt+F4 (close Chromium)"
echo " - Ctrl+Alt+T (open terminal, then: pkill -f kiosk)"
echo " - SSH in and run: pkill -f kiosk.sh"
wait $CHROME_PID
EOF EOF
chmod +x "$INSTALL_DIR/kiosk.sh" chmod +x "$INSTALL_DIR/kiosk.sh"
@ -339,7 +352,14 @@ print_summary() {
if [ "$KIOSK_MODE" = true ]; then if [ "$KIOSK_MODE" = true ]; then
echo -e " ${GREEN}Kiosk Mode:${NC} Enabled" echo -e " ${GREEN}Kiosk Mode:${NC} Enabled"
echo " OpenHamClock will auto-start on boot in fullscreen" echo " OpenHamClock will auto-start on boot in fullscreen"
echo " To disable: rm ~/.config/autostart/openhamclock-kiosk.desktop" echo ""
echo -e " ${YELLOW}Exit kiosk:${NC}"
echo " Alt+F4 Close Chromium"
echo " Ctrl+Alt+T Open terminal (then: pkill -f kiosk)"
echo " SSH: pkill -f kiosk.sh"
echo ""
echo -e " ${YELLOW}Disable auto-start:${NC}"
echo " rm ~/.config/autostart/openhamclock-kiosk.desktop"
echo "" echo ""
fi fi

@ -148,7 +148,7 @@ const CONFIG = {
// DX Cluster settings // DX Cluster settings
spotRetentionMinutes: parseInt(process.env.SPOT_RETENTION_MINUTES) || jsonConfig.dxCluster?.spotRetentionMinutes || 30, spotRetentionMinutes: parseInt(process.env.SPOT_RETENTION_MINUTES) || jsonConfig.dxCluster?.spotRetentionMinutes || 30,
dxClusterSource: jsonConfig.dxCluster?.source || 'auto', dxClusterSource: process.env.DX_CLUSTER_SOURCE || jsonConfig.dxCluster?.source || 'auto',
// API keys (don't expose to frontend) // API keys (don't expose to frontend)
_openWeatherApiKey: process.env.OPENWEATHER_API_KEY || '', _openWeatherApiKey: process.env.OPENWEATHER_API_KEY || '',
@ -983,7 +983,7 @@ let dxSpiderCache = { spots: [], timestamp: 0 };
const DXSPIDER_CACHE_TTL = 90000; // 90 seconds cache - reduces reconnection frequency const DXSPIDER_CACHE_TTL = 90000; // 90 seconds cache - reduces reconnection frequency
app.get('/api/dxcluster/spots', async (req, res) => { app.get('/api/dxcluster/spots', async (req, res) => {
const source = (req.query.source || 'auto').toLowerCase(); const source = (req.query.source || CONFIG.dxClusterSource || 'auto').toLowerCase();
// Helper function for HamQTH (HTTP-based, works everywhere) // Helper function for HamQTH (HTTP-based, works everywhere)
async function fetchHamQTH() { async function fetchHamQTH() {

@ -460,7 +460,7 @@ const App = () => {
</div> </div>
<div style={{ marginBottom: '6px' }}> <div style={{ marginBottom: '6px' }}>
<div style={{ color: '#888' }}>Kp</div> <div style={{ color: '#888' }}>Kp</div>
<div style={{ color: '#00ff00', fontSize: '16px', fontWeight: '700' }}>{spaceWeather?.data?.kIndex ?? '--'}</div> <div style={{ color: '#00ff00', fontSize: '16px', fontWeight: '700' }}>{solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}</div>
</div> </div>
<div style={{ marginBottom: '6px' }}> <div style={{ marginBottom: '6px' }}>
<div style={{ color: '#888' }}>Bz</div> <div style={{ color: '#888' }}>Bz</div>
@ -596,6 +596,7 @@ const App = () => {
localDate={localDate} localDate={localDate}
localWeather={localWeather} localWeather={localWeather}
spaceWeather={spaceWeather} spaceWeather={spaceWeather}
solarIndices={solarIndices}
use12Hour={use12Hour} use12Hour={use12Hour}
onTimeFormatToggle={handleTimeFormatToggle} onTimeFormatToggle={handleTimeFormatToggle}
onSettingsClick={() => setShowSettings(true)} onSettingsClick={() => setShowSettings(true)}

@ -279,7 +279,7 @@ export const DXFilterManager = ({ filters, onFilterChange, isOpen, onClose }) =>
<div> <div>
<div style={{ marginBottom: '16px' }}> <div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '13px', fontWeight: '600', color: 'var(--text-primary)', marginBottom: '8px' }}> <div style={{ fontSize: '13px', fontWeight: '600', color: 'var(--text-primary)', marginBottom: '8px' }}>
Exclude List - Hide these callsigns Exclude List - Hide DX callsigns beginning with:
</div> </div>
<div style={{ display: 'flex', gap: '8px' }}> <div style={{ display: 'flex', gap: '8px' }}>
<input <input

@ -12,6 +12,7 @@ export const Header = ({
localDate, localDate,
localWeather, localWeather,
spaceWeather, spaceWeather,
solarIndices,
use12Hour, use12Hour,
onTimeFormatToggle, onTimeFormatToggle,
onSettingsClick, onSettingsClick,
@ -37,8 +38,8 @@ export const Header = ({
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexShrink: 0 }}>
<span <span
style={{ style={{
fontSize: config.callsignSize > 0.1 && config.callsignSize <= 2 fontSize: config.headerSize > 0.1 && config.headerSize <= 2
? `${22 * config.callsignSize}px` ? `${22 * config.headerSize}px`
: "22px", fontWeight: '900', color: 'var(--accent-amber)', cursor: 'pointer', fontFamily: 'Orbitron, monospace', whiteSpace: 'nowrap' : "22px", fontWeight: '900', color: 'var(--accent-amber)', cursor: 'pointer', fontFamily: 'Orbitron, monospace', whiteSpace: 'nowrap'
}} }}
onClick={onSettingsClick} onClick={onSettingsClick}
@ -53,7 +54,9 @@ export const Header = ({
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 }}>
<span style={{ fontSize: '13px', color: 'var(--accent-cyan)', fontWeight: '600' }}>UTC</span> <span style={{ fontSize: '13px', color: 'var(--accent-cyan)', fontWeight: '600' }}>UTC</span>
<span style={{ <span style={{
fontSize: '24px', fontSize: config.headerSize > 0.1 && config.headerSize <= 2
? `${24 * config.headerSize}px`
: "24px",
fontWeight: '700', fontWeight: '700',
color: 'var(--accent-cyan)', color: 'var(--accent-cyan)',
fontFamily: 'JetBrains Mono, Consolas, monospace', fontFamily: 'JetBrains Mono, Consolas, monospace',
@ -70,7 +73,9 @@ export const Header = ({
> >
<span style={{ fontSize: '13px', color: 'var(--accent-amber)', fontWeight: '600' }}>LOCAL</span> <span style={{ fontSize: '13px', color: 'var(--accent-amber)', fontWeight: '600' }}>LOCAL</span>
<span style={{ <span style={{
fontSize: '24px', fontSize: config.headerSize > 0.1 && config.headerSize <= 2
? `${24 * config.headerSize}px`
: "24px",
fontWeight: '700', fontWeight: '700',
color: 'var(--accent-amber)', color: 'var(--accent-amber)',
fontFamily: 'JetBrains Mono, Consolas, monospace', fontFamily: 'JetBrains Mono, Consolas, monospace',
@ -90,8 +95,18 @@ export const Header = ({
const windLabel = localWeather.data.windUnit || 'mph'; const windLabel = localWeather.data.windUnit || 'mph';
return ( return (
<div title={`${localWeather.data.description} • Wind: ${localWeather.data.windSpeed} ${windLabel}`}> <div title={`${localWeather.data.description} • Wind: ${localWeather.data.windSpeed} ${windLabel}`}>
<span style={{ marginRight: '3px' }}>{localWeather.data.icon}</span> <span style={{ marginRight: '3px',
<span style={{ color: 'var(--accent-cyan)', fontWeight: '600' }}> fontSize: config.headerSize > 0.1 && config.headerSize <= 2
? `${12 * config.headerSize}px`
: "12px",
}}>
{localWeather.data.icon}
</span>
<span style={{ color: 'var(--accent-cyan)', fontWeight: '600',
fontSize: config.headerSize > 0.1 && config.headerSize <= 2
? `${12 * config.headerSize}px`
: "12px",
}}>
{tempF}°F/{tempC}°C {tempF}°F/{tempC}°C
</span> </span>
</div> </div>
@ -99,17 +114,17 @@ export const Header = ({
})()} })()}
<div> <div>
<span style={{ color: 'var(--text-muted)' }}>SFI </span> <span style={{ color: 'var(--text-muted)' }}>SFI </span>
<span style={{ color: 'var(--accent-amber)', fontWeight: '700' }}>{spaceWeather?.data?.solarFlux || '--'}</span> <span style={{ color: 'var(--accent-amber)', fontWeight: '700' }}>{solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'}</span>
</div> </div>
<div> <div>
<span style={{ color: 'var(--text-muted)' }}>K </span> <span style={{ color: 'var(--text-muted)' }}>K </span>
<span style={{ color: parseInt(spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> <span style={{ color: parseInt(solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}>
{spaceWeather?.data?.kIndex ?? '--'} {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
</span> </span>
</div> </div>
<div> <div>
<span style={{ color: 'var(--text-muted)' }}>SSN </span> <span style={{ color: 'var(--text-muted)' }}>SSN </span>
<span style={{ color: 'var(--accent-cyan)', fontWeight: '700' }}>{spaceWeather?.data?.sunspotNumber || '--'}</span> <span style={{ color: 'var(--accent-cyan)', fontWeight: '700' }}>{solarIndices?.data?.ssn?.current || spaceWeather?.data?.sunspotNumber || '--'}</span>
</div> </div>
</div> </div>

@ -9,7 +9,7 @@ import { LANGUAGES } from '../lang/i18n.js';
export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
const [callsign, setCallsign] = useState(config?.callsign || ''); const [callsign, setCallsign] = useState(config?.callsign || '');
const [callsignSize, setCallsignSize] = useState(config?.callsignSize || 1.0); const [headerSize, setheaderSize] = useState(config?.headerSize || 1.0);
const [gridSquare, setGridSquare] = useState(''); const [gridSquare, setGridSquare] = useState('');
const [lat, setLat] = useState(config?.location?.lat || 0); const [lat, setLat] = useState(config?.location?.lat || 0);
const [lon, setLon] = useState(config?.location?.lon || 0); const [lon, setLon] = useState(config?.location?.lon || 0);
@ -26,7 +26,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
useEffect(() => { useEffect(() => {
if (config) { if (config) {
setCallsign(config.callsign || ''); setCallsign(config.callsign || '');
setCallsignSize(config.callsignSize || 1.0) setheaderSize(config.headerSize || 1.0)
setLat(config.location?.lat || 0); setLat(config.location?.lat || 0);
setLon(config.location?.lon || 0); setLon(config.location?.lon || 0);
setTheme(config.theme || 'dark'); setTheme(config.theme || 'dark');
@ -149,7 +149,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
onSave({ onSave({
...config, ...config,
callsign: callsign.toUpperCase(), callsign: callsign.toUpperCase(),
callsignSize: callsignSize, headerSize: headerSize,
location: { lat: parseFloat(lat), lon: parseFloat(lon) }, location: { lat: parseFloat(lat), lon: parseFloat(lon) },
theme, theme,
layout, layout,
@ -309,15 +309,15 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
<div style={{ marginBottom: '20px'}}> <div style={{ marginBottom: '20px'}}>
<div> <div>
<label style={{ display: 'block', marginBottom: '6px', color: 'var(--text-muted)', fontSize: '11px', textTransform: 'uppercase' }}> <label style={{ display: 'block', marginBottom: '6px', color: 'var(--text-muted)', fontSize: '11px', textTransform: 'uppercase' }}>
{t('station.settings.callsignSize')} {t('station.settings.headerSize')}
</label> </label>
<input <input
type="number" type="number"
step="0.1" step="0.1"
value={isNaN(lat) ? '' : callsignSize} value={isNaN(lat) ? '' : headerSize}
onChange={(e) => { onChange={(e) => {
if (e.target.value >= 0.1 && e.target.value <= 2.0) { if (e.target.value >= 0.1 && e.target.value <= 2.0) {
setCallsignSize(e.target.value) setheaderSize(e.target.value)
}}} }}}
style={{ style={{
width: '100%', width: '100%',

@ -26,17 +26,15 @@ export const useDXCluster = (source = 'auto', filters = {}) => {
// Watchlist only mode - must match watchlist // Watchlist only mode - must match watchlist
if (filters.watchlistOnly && filters.watchlist?.length > 0) { if (filters.watchlistOnly && filters.watchlist?.length > 0) {
const matchesWatchlist = filters.watchlist.some(w => const matchesWatchlist = filters.watchlist.some(w =>
spot.call?.toUpperCase().includes(w.toUpperCase()) || spot.call?.toUpperCase().includes(w.toUpperCase())
spot.spotter?.toUpperCase().includes(w.toUpperCase())
); );
if (!matchesWatchlist) return false; if (!matchesWatchlist) return false;
} }
// Exclude list - hide matching calls // Exclude list - hide matching calls - match the call as a prefix
if (filters.excludeList?.length > 0) { if (filters.excludeList?.length > 0) {
const isExcluded = filters.excludeList.some(exc => const isExcluded = filters.excludeList.some(exc =>
spot.call?.toUpperCase().includes(exc.toUpperCase()) || spot.call?.toUpperCase().startsWith(exc.toUpperCase())
spot.spotter?.toUpperCase().includes(exc.toUpperCase())
); );
if (isExcluded) return false; if (isExcluded) return false;
} }
@ -89,7 +87,7 @@ export const useDXCluster = (source = 'auto', filters = {}) => {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const response = await fetch('/api/dxcluster/spots'); const response = await fetch(`/api/dxcluster/spots?source=${encodeURIComponent(source)}`);
if (response.ok) { if (response.ok) {
const newSpots = await response.json(); const newSpots = await response.json();

@ -14,20 +14,20 @@ const API_URL = '/api/wsjtx';
const DECODES_URL = '/api/wsjtx/decodes'; const DECODES_URL = '/api/wsjtx/decodes';
// Generate or retrieve persistent session ID // Generate or retrieve persistent session ID
// NOTE: Kept short (8 chars) intentionally — long UUIDs in query strings
// trigger false positives in Bitdefender and similar security software
function getSessionId() { function getSessionId() {
const KEY = 'ohc-wsjtx-session'; const KEY = 'ohc-wsjtx-session';
const generate = () => Math.random().toString(36).substring(2, 10);
try { try {
let id = localStorage.getItem(KEY); let id = localStorage.getItem(KEY);
if (id && id.length >= 16) return id; if (id && id.length >= 8) return id;
// Generate a random ID id = generate();
id = (typeof crypto !== 'undefined' && crypto.randomUUID)
? crypto.randomUUID()
: Math.random().toString(36).substring(2) + Date.now().toString(36) + Math.random().toString(36).substring(2);
localStorage.setItem(KEY, id); localStorage.setItem(KEY, id);
return id; return id;
} catch { } catch {
// Fallback for privacy browsers that block localStorage // Fallback for privacy browsers that block localStorage
return Math.random().toString(36).substring(2) + Date.now().toString(36) + Math.random().toString(36).substring(2); return generate();
} }
} }

@ -9,6 +9,7 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Höhe (m)", "station.settings.altitude": "Höhe (m)",
"station.settings.antenna": "Antenne", "station.settings.antenna": "Antenne",
"station.settings.button.save": "Einstellungen Speichern", "station.settings.button.save": "Einstellungen Speichern",
@ -45,5 +46,12 @@
"station.settings.useLocation": "📍 Meinen Standort verwenden", "station.settings.useLocation": "📍 Meinen Standort verwenden",
"station.settings.useLocation.error1": "Standort konnte nicht ermittelt werden. Bitte manuell eingeben.", "station.settings.useLocation.error1": "Standort konnte nicht ermittelt werden. Bitte manuell eingeben.",
"station.settings.useLocation.error2": "Geolokalisierung wird von deinem Browser nicht unterstützt.", "station.settings.useLocation.error2": "Geolokalisierung wird von deinem Browser nicht unterstützt.",
"station.settings.welcome": "👋 Willkommen bei OpenHamClock!" "station.settings.welcome": "👋 Willkommen bei OpenHamClock!",
"plugins.layers.aurora.name": "Aurora-Vorhersage",
"plugins.layers.aurora.description": "NOAA-OVATION-Aurora-Wahrscheinlichkeitsvorhersage (30 Min.)",
"plugins.layers.earthquakes.name": "Erdbeben",
"plugins.layers.earthquakes.description": "Live-USGS-Erdbebendaten (M2,5+ der letzten 24 Stunden)",
"plugins.layers.wxradar.name": "Wetterradar",
"plugins.layers.wxradar.description": "NEXRAD-Wetterradar-Überlagerung für Nordamerika",
"plugins.layers.wxradar.attribution": "Wetterdaten © Iowa State University Mesonet"
} }

@ -9,12 +9,13 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Altitude (m)", "station.settings.altitude": "Altitude (m)",
"station.settings.antenna": "Antenna", "station.settings.antenna": "Antenna",
"station.settings.button.save": "Save Settings", "station.settings.button.save": "Save Settings",
"station.settings.button.save.confirm": "Settings saved to your browser", "station.settings.button.save.confirm": "Settings saved to your browser",
"station.settings.callsign": "Your Callsign", "station.settings.callsign": "Your Callsign",
"station.settings.callsignSize": "Your Callsign's size", "station.settings.headerSize": "Your Callsign's size",
"station.settings.describe": "Enter your callsign and grid square to get started. Settings are saved in your browser.", "station.settings.describe": "Enter your callsign and grid square to get started. Settings are saved in your browser.",
"station.settings.dx.describe": "→ Real-time DX Spider feed via our dedicated proxy service", "station.settings.dx.describe": "→ Real-time DX Spider feed via our dedicated proxy service",
"station.settings.dx.option1": "⭐ DX Spider Proxy (Recommended)", "station.settings.dx.option1": "⭐ DX Spider Proxy (Recommended)",
@ -46,5 +47,12 @@
"station.settings.useLocation": "📍 Use my current location", "station.settings.useLocation": "📍 Use my current location",
"station.settings.useLocation.error1": "Could not get location. Please enter manually.", "station.settings.useLocation.error1": "Could not get location. Please enter manually.",
"station.settings.useLocation.error2": "Geolocation is not supported by your browser.", "station.settings.useLocation.error2": "Geolocation is not supported by your browser.",
"station.settings.welcome": "👋 Welcome to OpenHamClock!" "station.settings.welcome": "👋 Welcome to OpenHamClock!",
"plugins.layers.aurora.name": "Aurora Forecast",
"plugins.layers.aurora.description": "NOAA OVATION aurora probability forecast (30-min)",
"plugins.layers.earthquakes.name": "Earthquakes",
"plugins.layers.earthquakes.description": "Live USGS earthquake data (M2.5+ from last 24 hours)",
"plugins.layers.wxradar.name": "Weather Radar",
"plugins.layers.wxradar.description": "NEXRAD weather radar overlay for North America",
"plugins.layers.wxradar.attribution": "Weather data © Iowa State University Mesonet"
} }

@ -9,12 +9,13 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Altitud (m)", "station.settings.altitude": "Altitud (m)",
"station.settings.antenna": "Antena", "station.settings.antenna": "Antena",
"station.settings.button.save": "Guardar Configuración", "station.settings.button.save": "Guardar Configuración",
"station.settings.button.save.confirm": "La configuración se guarda en tu navegador", "station.settings.button.save.confirm": "La configuración se guarda en tu navegador",
"station.settings.callsign": "Tu Indicativo", "station.settings.callsign": "Tu Indicativo",
"station.settings.callsignSize": "El tamaño de tu Indicativo", "station.settings.headerSize": "El tamaño de tu Indicativo",
"station.settings.describe": "Ingresa tu indicativo y cuadrícula para comenzar. Tu configuración se guardará en el navegador.", "station.settings.describe": "Ingresa tu indicativo y cuadrícula para comenzar. Tu configuración se guardará en el navegador.",
"station.settings.dx.describe": "→ Feed en tiempo real de DX Spider a través de nuestro servicio proxy dedicado", "station.settings.dx.describe": "→ Feed en tiempo real de DX Spider a través de nuestro servicio proxy dedicado",
"station.settings.dx.option1": "⭐ Proxy DX Spider (Recomendado)", "station.settings.dx.option1": "⭐ Proxy DX Spider (Recomendado)",
@ -46,5 +47,12 @@
"station.settings.useLocation": "📍 Usar Mi Ubicación Actual", "station.settings.useLocation": "📍 Usar Mi Ubicación Actual",
"station.settings.useLocation.error1": "No se pudo obtener la ubicación. Por favor ingrésala manualmente.", "station.settings.useLocation.error1": "No se pudo obtener la ubicación. Por favor ingrésala manualmente.",
"station.settings.useLocation.error2": "La geolocalización no es compatible con tu navegador.", "station.settings.useLocation.error2": "La geolocalización no es compatible con tu navegador.",
"station.settings.welcome": "👋 ¡Bienvenido a OpenHamClock!" "station.settings.welcome": "👋 ¡Bienvenido a OpenHamClock!",
"plugins.layers.aurora.name": "Pronóstico de auroras",
"plugins.layers.aurora.description": "Pronóstico de probabilidad de auroras NOAA OVATION (30 min)",
"plugins.layers.earthquakes.name": "Terremotos",
"plugins.layers.earthquakes.description": "Datos sísmicos en vivo del USGS (M2.5+ de las últimas 24 horas)",
"plugins.layers.wxradar.name": "Radar meteorológico",
"plugins.layers.wxradar.description": "Superposición del radar meteorológico NEXRAD para Norteamérica",
"plugins.layers.wxradar.attribution": "Datos meteorológicos © Iowa State University Mesonet"
} }

@ -9,6 +9,7 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Altitude (m)", "station.settings.altitude": "Altitude (m)",
"station.settings.antenna": "Antenne", "station.settings.antenna": "Antenne",
"station.settings.button.save": "Enregistrer les paramètres", "station.settings.button.save": "Enregistrer les paramètres",
@ -45,5 +46,12 @@
"station.settings.useLocation": "📍 Utiliser ma position actuelle", "station.settings.useLocation": "📍 Utiliser ma position actuelle",
"station.settings.useLocation.error1": "Impossible d'obtenir la position. Veuillez entrer manuellement.", "station.settings.useLocation.error1": "Impossible d'obtenir la position. Veuillez entrer manuellement.",
"station.settings.useLocation.error2": "La géolocalisation n'est pas prise en charge par votre navigateur.", "station.settings.useLocation.error2": "La géolocalisation n'est pas prise en charge par votre navigateur.",
"station.settings.welcome": "👋 Bienvenue sur OpenHamClock !" "station.settings.welcome": "👋 Bienvenue sur OpenHamClock !",
"plugins.layers.aurora.name": "Prévision daurores",
"plugins.layers.aurora.description": "Prévision de probabilité daurores NOAA OVATION (30 min)",
"plugins.layers.earthquakes.name": "Séismes",
"plugins.layers.earthquakes.description": "Données sismiques USGS en direct (M2,5+ sur les dernières 24 heures)",
"plugins.layers.wxradar.name": "Radar météo",
"plugins.layers.wxradar.description": "Surcouche du radar météo NEXRAD pour lAmérique du Nord",
"plugins.layers.wxradar.attribution": "Données météo © Iowa State University Mesonet"
} }

@ -10,6 +10,7 @@ import translationPT from './pt.json';
import translationJA from './ja.json'; import translationJA from './ja.json';
import translationIT from './it.json'; import translationIT from './it.json';
import translationNL from './nl.json'; import translationNL from './nl.json';
import translationKO from './ko.json';
export const LANGUAGES = [ export const LANGUAGES = [
{ code: 'en', name: 'English', flag: '🇬🇧' }, { code: 'en', name: 'English', flag: '🇬🇧' },
@ -19,6 +20,7 @@ export const LANGUAGES = [
{ code: 'nl', name: 'Nederlands', flag: '🇳🇱' }, { code: 'nl', name: 'Nederlands', flag: '🇳🇱' },
{ code: 'pt', name: 'Português', flag: '🇧🇷' }, { code: 'pt', name: 'Português', flag: '🇧🇷' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' }, { code: 'ja', name: '日本語', flag: '🇯🇵' },
{ code: 'ko', name: '한국어', flag: '🇰🇷' },
{ code: 'it', name: 'Italiano', flag: '🇮🇹' } { code: 'it', name: 'Italiano', flag: '🇮🇹' }
]; ];
@ -30,6 +32,7 @@ export const resources = {
nl: { translation: translationNL }, nl: { translation: translationNL },
pt: { translation: translationPT }, pt: { translation: translationPT },
ja: { translation: translationJA }, ja: { translation: translationJA },
ko: { translation: translationKO },
it: { translation: translationIT } it: { translation: translationIT }
}; };

@ -9,6 +9,7 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Altitudine (m)", "station.settings.altitude": "Altitudine (m)",
"station.settings.antenna": "Antenna", "station.settings.antenna": "Antenna",
"station.settings.button.save": "Salva Impostazioni", "station.settings.button.save": "Salva Impostazioni",
@ -45,5 +46,12 @@
"station.settings.useLocation": "📍 Usa la Mia Posizione Attuale", "station.settings.useLocation": "📍 Usa la Mia Posizione Attuale",
"station.settings.useLocation.error1": "Impossibile ottenere la posizione. Inseriscila manualmente.", "station.settings.useLocation.error1": "Impossibile ottenere la posizione. Inseriscila manualmente.",
"station.settings.useLocation.error2": "La geolocalizzazione non è supportata dal tuo browser.", "station.settings.useLocation.error2": "La geolocalizzazione non è supportata dal tuo browser.",
"station.settings.welcome": "👋 Benvenuto su OpenHamClock!" "station.settings.welcome": "👋 Benvenuto su OpenHamClock!",
"plugins.layers.aurora.name": "Previsione dellaurora",
"plugins.layers.aurora.description": "Previsione di probabilità dellaurora NOAA OVATION (30 min)",
"plugins.layers.earthquakes.name": "Terremoti",
"plugins.layers.earthquakes.description": "Dati sismici USGS in tempo reale (M2,5+ delle ultime 24 ore)",
"plugins.layers.wxradar.name": "Radar meteorologico",
"plugins.layers.wxradar.description": "Sovrapposizione del radar meteorologico NEXRAD per il Nord America",
"plugins.layers.wxradar.attribution": "Dati meteo © Iowa State University Mesonet"
} }

@ -9,6 +9,7 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "標高 (m)", "station.settings.altitude": "標高 (m)",
"station.settings.antenna": "アンテナ", "station.settings.antenna": "アンテナ",
"station.settings.button.save": "設定を保存", "station.settings.button.save": "設定を保存",
@ -45,5 +46,12 @@
"station.settings.useLocation": "📍 現在地を使用", "station.settings.useLocation": "📍 現在地を使用",
"station.settings.useLocation.error1": "位置情報を取得できません。手動で入力してください。", "station.settings.useLocation.error1": "位置情報を取得できません。手動で入力してください。",
"station.settings.useLocation.error2": "お使いのブラウザはジオロケーションに対応していません。", "station.settings.useLocation.error2": "お使いのブラウザはジオロケーションに対応していません。",
"station.settings.welcome": "👋 OpenHamClockへようこそ" "station.settings.welcome": "👋 OpenHamClockへようこそ",
"plugins.layers.aurora.name": "オーロラ予報",
"plugins.layers.aurora.description": "NOAA OVATION オーロラ出現確率予報30分",
"plugins.layers.earthquakes.name": "地震",
"plugins.layers.earthquakes.description": "USGSのリアルタイム地震データ過去24時間のM2.5以上)",
"plugins.layers.wxradar.name": "気象レーダー",
"plugins.layers.wxradar.description": "北米向けNEXRAD気象レーダーのオーバーレイ",
"plugins.layers.wxradar.attribution": "気象データ © Iowa State University Mesonet"
} }

@ -0,0 +1,57 @@
{
"cancel": "취소",
"station.settings.language": "언어",
"station.settings.language.en": "English",
"station.settings.language.fr": "Français",
"station.settings.language.es": "Español",
"station.settings.language.de": "Deutsch",
"station.settings.language.pt": "Português",
"station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "고도 (m)",
"station.settings.antenna": "안테나",
"station.settings.button.save": "설정 저장하기",
"station.settings.button.save.confirm": "설정이 브라우저에 저장되었습니다.",
"station.settings.callsign": "콜사인",
"station.settings.describe": "콜사인과 그리드 스퀘어(예: PM37mp)를 입력하여 시작하세요. 설정은 브라우저에 저장됩니다.",
"station.settings.dx.describe": "→ 자체 프록시 서버를 이용한 실시간 DX Spider 연동",
"station.settings.dx.option1": "⭐ DX Spider 프록시 (권장)",
"station.settings.dx.option2": "HamQTH Cluster",
"station.settings.dx.option3": "DXWatch",
"station.settings.dx.option4": "자동 (전부 시도하기)",
"station.settings.dx.title": "DX Cluster 데이터 서버",
"station.settings.layout": "레이아웃",
"station.settings.layout.classic": "클래식",
"station.settings.layout.classic.describe": "→ HamClock과 유사한 레이아웃",
"station.settings.layout.modern": "모던",
"station.settings.layout.modern.describe": "→ 반응형 모던 그리드 레이아웃",
"station.settings.latitude": "위도",
"station.settings.locator": "그리드 스퀘어 (또는 아래에 직접 위도/경도 입력)",
"station.settings.longitude": "경도",
"station.settings.power": "출력 (W)",
"station.settings.theme": "테마",
"station.settings.theme.dark": "다크",
"station.settings.theme.dark.describe": "→ 현대적인 다크 테마 (기본)",
"station.settings.theme.legacy": "CRT",
"station.settings.theme.legacy.describe": "→ 초록 CRT 터미널 스타일",
"station.settings.theme.light": "라이트",
"station.settings.theme.light.describe": "→ 낮 시간 사용을 위한 밝은 테마",
"station.settings.theme.retro": "레트로",
"station.settings.theme.retro.describe": "→ 90년대 Windows 레트로 스타일",
"station.settings.timezone": "시간대",
"station.settings.title": "무선국 설정",
"station.settings.tip.env": "💡 팁: 영구적인 데이터 저장을 위해 <envExample>.env.example</envExample> 파일을 복사해 <env>.env</env> 파일을 덮어쓰고 CALLSIGN 과 LOCATOR 필드를 채우세요.",
"station.settings.useLocation": "📍 현재 내 위치 사용",
"station.settings.useLocation.error1": "위치 정보를 가져올 수 없습니다. 위도와 경도를 수동으로 입력해 주세요.",
"station.settings.useLocation.error2": "브라우저가 위치 정보 기능을 지원하지 않습니다.",
"station.settings.welcome": "👋 OpenHamClock에 오신 것을 환영합니다!",
"plugins.layers.aurora.name": "오로라 예보",
"plugins.layers.aurora.description": "NOAA OVATION 오로라 확률 예보 (30분)",
"plugins.layers.earthquakes.name": "실시간 지진 현황",
"plugins.layers.earthquakes.description": "USGS 실시간 지진 데이터 (지난 24시간 동안 일어난 규모 M2.5 이상의 지진)",
"plugins.layers.wxradar.name": "기상 레이더",
"plugins.layers.wxradar.description": "북아메리카 지역 NEXRAD 기상 레이더 오버레이",
"plugins.layers.wxradar.attribution": "기상 데이터 © Iowa State University Mesonet"
}

@ -9,6 +9,7 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiaans", "station.settings.language.it": "Italiaans",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Hoogte (m)", "station.settings.altitude": "Hoogte (m)",
"station.settings.antenna": "Antenne", "station.settings.antenna": "Antenne",
"station.settings.button.save": "Bewaar Settings", "station.settings.button.save": "Bewaar Settings",
@ -45,5 +46,12 @@
"station.settings.useLocation": "📍 Gebruik mijn huidige locatie", "station.settings.useLocation": "📍 Gebruik mijn huidige locatie",
"station.settings.useLocation.error1": "Kan niet de locatie vinden. Graag handmatig ingeven.", "station.settings.useLocation.error1": "Kan niet de locatie vinden. Graag handmatig ingeven.",
"station.settings.useLocation.error2": "Geolocation is niet beschikbaar op je browser.", "station.settings.useLocation.error2": "Geolocation is niet beschikbaar op je browser.",
"station.settings.welcome": "👋 Welkom bij OpenHamClock!" "station.settings.welcome": "👋 Welkom bij OpenHamClock!",
"plugins.layers.aurora.name": "Auroravoorspelling",
"plugins.layers.aurora.description": "NOAA OVATION-voorspelling van aurorakans (30 min)",
"plugins.layers.earthquakes.name": "Aardbevingen",
"plugins.layers.earthquakes.description": "Live USGS-aardbevingsgegevens (M2,5+ van de afgelopen 24 uur)",
"plugins.layers.wxradar.name": "Weerradar",
"plugins.layers.wxradar.description": "NEXRAD-weerradaroverlay voor Noord-Amerika",
"plugins.layers.wxradar.attribution": "Weergegevens © Iowa State University Mesonet"
} }

@ -9,6 +9,7 @@
"station.settings.language.ja": "日本語", "station.settings.language.ja": "日本語",
"station.settings.language.it": "Italiano", "station.settings.language.it": "Italiano",
"station.settings.language.nl": "Nederlands", "station.settings.language.nl": "Nederlands",
"station.settings.language.ko": "한국어",
"station.settings.altitude": "Altitude (m)", "station.settings.altitude": "Altitude (m)",
"station.settings.antenna": "Antena", "station.settings.antenna": "Antena",
"station.settings.button.save": "Salvar Configurações", "station.settings.button.save": "Salvar Configurações",
@ -45,5 +46,12 @@
"station.settings.useLocation": "📍 Usar Minha Localização Atual", "station.settings.useLocation": "📍 Usar Minha Localização Atual",
"station.settings.useLocation.error1": "Não foi possível obter a localização. Por favor, insira manualmente.", "station.settings.useLocation.error1": "Não foi possível obter a localização. Por favor, insira manualmente.",
"station.settings.useLocation.error2": "Geolocalização não é suportada pelo seu navegador.", "station.settings.useLocation.error2": "Geolocalização não é suportada pelo seu navegador.",
"station.settings.welcome": "👋 Bem-vindo ao OpenHamClock!" "station.settings.welcome": "👋 Bem-vindo ao OpenHamClock!",
"plugins.layers.aurora.name": "Previsão de aurora",
"plugins.layers.aurora.description": "Previsão de probabilidade de aurora NOAA OVATION (30 min)",
"plugins.layers.earthquakes.name": "Terremotos",
"plugins.layers.earthquakes.description": "Dados sísmicos do USGS ao vivo (M2,5+ das últimas 24 horas)",
"plugins.layers.wxradar.name": "Radar meteorológico",
"plugins.layers.wxradar.description": "Sobreposição do radar meteorológico NEXRAD para a América do Norte",
"plugins.layers.wxradar.attribution": "Dados meteorológicos © Iowa State University Mesonet"
} }

@ -1,3 +1,5 @@
import i18n from '../../lang/i18n';
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
// NOAA OVATION Aurora Forecast - JSON grid data // NOAA OVATION Aurora Forecast - JSON grid data
@ -7,8 +9,8 @@ import { useState, useEffect, useRef } from 'react';
export const metadata = { export const metadata = {
id: 'aurora', id: 'aurora',
name: 'Aurora Forecast', name: i18n.t('plugins.layers.aurora.name'),
description: 'NOAA OVATION aurora probability forecast (30-min)', description: i18n.t('plugins.layers.aurora.description'),
icon: '🌌', icon: '🌌',
category: 'space-weather', category: 'space-weather',
defaultEnabled: false, defaultEnabled: false,

@ -1,3 +1,5 @@
import i18n from '../../lang/i18n';
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
//Scaled markers - Bigger circles for stronger quakes //Scaled markers - Bigger circles for stronger quakes
@ -11,8 +13,8 @@ import { useState, useEffect, useRef } from 'react';
export const metadata = { export const metadata = {
id: 'earthquakes', id: 'earthquakes',
name: 'Earthquakes', name: i18n.t('plugins.layers.earthquakes.name'),
description: 'Live USGS earthquake data (all earthquakes from last hour) with animated detection', description: i18n.t('plugins.layers.earthquakes.description'),
icon: '🌋', icon: '🌋',
category: 'geology', category: 'geology',
defaultEnabled: false, defaultEnabled: false,

@ -1,9 +1,11 @@
import i18n from '../../lang/i18n';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
export const metadata = { export const metadata = {
id: 'wxradar', id: 'wxradar',
name: 'Weather Radar', name: i18n.t('plugins.layers.wxradar.name'),
description: 'NEXRAD weather radar overlay for North America', description: i18n.t('plugins.layers.wxradar.description'),
icon: '☁️', icon: '☁️',
category: 'weather', category: 'weather',
defaultEnabled: false, defaultEnabled: false,
@ -21,7 +23,7 @@ export function useLayer({ enabled = false, opacity = 0.6, map = null }) {
layers: 'nexrad-n0r-900913', layers: 'nexrad-n0r-900913',
format: 'image/png', format: 'image/png',
transparent: true, transparent: true,
attribution: 'Weather data © Iowa State University Mesonet', attribution: i18n.t('plugins.layers.wxradar.attribution'),
opacity: opacity, opacity: opacity,
zIndex: 200 zIndex: 200
} }

@ -242,8 +242,7 @@ export const filterDXPaths = (paths, filters) => {
// Exclude list - hide matching callsigns // Exclude list - hide matching callsigns
if (filters.excludeList?.length > 0) { if (filters.excludeList?.length > 0) {
const isExcluded = filters.excludeList.some(e => const isExcluded = filters.excludeList.some(e =>
path.dxCall?.toUpperCase().includes(e.toUpperCase()) || path.dxCall?.toUpperCase().startsWith(e.toUpperCase())
path.spotter?.toUpperCase().includes(e.toUpperCase())
); );
if (isExcluded) return false; if (isExcluded) return false;
} }

@ -10,7 +10,7 @@
export const DEFAULT_CONFIG = { export const DEFAULT_CONFIG = {
callsign: 'N0CALL', callsign: 'N0CALL',
callsignSize: 1.0, // Float multiplies base px size (0.1 to 2.0) headerSize: 1.0, // Float multiplies base px size (0.1 to 2.0)
locator: '', locator: '',
location: { lat: 40.0150, lon: -105.2705 }, // Boulder, CO (default) location: { lat: 40.0150, lon: -105.2705 }, // Boulder, CO (default)
defaultDX: { lat: 35.6762, lon: 139.6503 }, // Tokyo defaultDX: { lat: 35.6762, lon: 139.6503 }, // Tokyo

@ -130,9 +130,9 @@ export const getMoonPosition = (date) => {
export const getMoonPhase = (date) => { export const getMoonPhase = (date) => {
const JD = date.getTime() / 86400000 + 2440587.5; const JD = date.getTime() / 86400000 + 2440587.5;
const T = (JD - 2451545.0) / 36525; const T = (JD - 2451545.0) / 36525;
const D = (297.850 + 445267.1115 * T) % 360; // Mean elongation const D = (297.850 + 445267.1115 * T) % 360; // Mean elongation: 0=new, 180=full
// Phase angle (simplified) // Normalize to 0-1 range (0=new, 0.5=full)
const phase = ((D + 180) % 360) / 360; const phase = (((D % 360) + 360) % 360) / 360;
return phase; return phase;
}; };

Loading…
Cancel
Save

Powered by TurnKey Linux.