pota and logging issues

pull/79/head
accius 1 day ago
parent 65c54e89d6
commit 70fa3c564c

@ -49,8 +49,35 @@ let buffer = '';
let reconnectTimer = null;
let keepAliveTimer = null;
// Logging helper
// Logging helper with log levels
// LOG_LEVEL: 'debug' = verbose, 'info' = normal, 'warn' = warnings+errors only
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase();
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
const currentLogLevel = LOG_LEVELS[LOG_LEVEL] ?? LOG_LEVELS.info;
// Map log categories to levels
const CATEGORY_LEVELS = {
'SPOT': 'debug', // Per-spot logging is debug-only
'CLEANUP': 'debug', // Periodic cleanup is debug-only
'KEEPALIVE': 'debug', // Keepalive pings are debug-only
'CMD': 'debug', // Command logging is debug-only
'AUTH': 'info', // Auth events are informational
'CONNECT': 'info', // Connection events are informational
'CLOSE': 'info',
'RECONNECT': 'info',
'FAILOVER': 'info',
'API': 'info',
'START': 'info',
'CONFIG': 'info',
'SHUTDOWN': 'info',
'ERROR': 'warn',
'TIMEOUT': 'warn',
};
const log = (level, message, data = null) => {
const categoryLevel = LOG_LEVELS[CATEGORY_LEVELS[level] || 'info'] ?? LOG_LEVELS.info;
if (categoryLevel < currentLogLevel) return;
const timestamp = new Date().toISOString();
const logLine = `[${timestamp}] [${level}] ${message}`;
if (data) {

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
/**
* POTAPanel Component
* Displays Parks on the Air activations with ON/OFF toggle (compact version)
* Displays Parks on the Air activations with ON/OFF toggle
*/
import React from 'react';
@ -14,14 +14,14 @@ export const POTAPanel = ({ data, loading, showOnMap, onToggleMap }) => {
marginBottom: '6px',
fontSize: '11px'
}}>
<span>POTA ACTIVATORS</span>
<span>POTA ACTIVATORS {data?.length > 0 ? `(${data.length})` : ''}</span>
<button
onClick={onToggleMap}
title={showOnMap ? 'Hide POTA activators on map' : 'Show POTA activators on map'}
style={{
background: showOnMap ? 'rgba(170, 102, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${showOnMap ? '#aa66ff' : '#666'}`,
color: showOnMap ? '#aa66ff' : '#888',
background: showOnMap ? 'rgba(68, 204, 68, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${showOnMap ? '#44cc44' : '#666'}`,
color: showOnMap ? '#44cc44' : '#888',
padding: '1px 6px',
borderRadius: '3px',
fontSize: '9px',
@ -40,26 +40,29 @@ export const POTAPanel = ({ data, loading, showOnMap, onToggleMap }) => {
</div>
) : data && data.length > 0 ? (
<div style={{ fontSize: '10px', fontFamily: 'JetBrains Mono, monospace' }}>
{data.slice(0, 5).map((spot, i) => (
{data.map((spot, i) => (
<div
key={`${spot.call}-${i}`}
key={`${spot.call}-${spot.ref}-${i}`}
style={{
display: 'grid',
gridTemplateColumns: '60px 60px 1fr',
gap: '6px',
gridTemplateColumns: '62px 50px 58px 1fr',
gap: '4px',
padding: '3px 0',
borderBottom: i < Math.min(data.length, 5) - 1 ? '1px solid var(--border-color)' : 'none'
borderBottom: i < data.length - 1 ? '1px solid var(--border-color)' : 'none'
}}
>
<span style={{ color: 'var(--accent-purple)', fontWeight: '600' }}>
<span style={{ color: '#44cc44', fontWeight: '600', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{spot.call}
</span>
<span style={{ color: 'var(--text-muted)' }}>
{spot.ref}
<span style={{ color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{spot.locationDesc || spot.ref}
</span>
<span style={{ color: 'var(--accent-cyan)', textAlign: 'right' }}>
{spot.freq}
</span>
<span style={{ color: 'var(--text-muted)', textAlign: 'right', fontSize: '9px' }}>
{spot.time}
</span>
</div>
))}
</div>

@ -425,7 +425,7 @@ export const WorldMap = ({
iconAnchor: [7, 14]
});
const marker = L.marker([spot.lat, spot.lon], { icon: triangleIcon })
.bindPopup(`<b style="color:#44cc44">${spot.call}</b><br>${spot.ref}<br>${spot.freq} ${spot.mode}`)
.bindPopup(`<b style="color:#44cc44">${spot.call}</b><br><span style="color:#888">${spot.ref}</span> ${spot.locationDesc || ''}<br>${spot.name ? `<i>${spot.name}</i><br>` : ''}${spot.freq} ${spot.mode || ''} <span style="color:#888">${spot.time || ''}</span>`)
.addTo(map);
potaMarkersRef.current.push(marker);

@ -4,6 +4,29 @@
*/
import { useState, useEffect } from 'react';
// Convert grid square to lat/lon
function gridToLatLon(grid) {
if (!grid || grid.length < 4) return null;
const g = grid.toUpperCase();
const lon = (g.charCodeAt(0) - 65) * 20 - 180;
const lat = (g.charCodeAt(1) - 65) * 10 - 90;
const lonMin = parseInt(g[2]) * 2;
const latMin = parseInt(g[3]) * 1;
let finalLon = lon + lonMin + 1;
let finalLat = lat + latMin + 0.5;
if (grid.length >= 6) {
const lonSec = (g.charCodeAt(4) - 65) * (2/24);
const latSec = (g.charCodeAt(5) - 65) * (1/24);
finalLon = lon + lonMin + lonSec + (1/24);
finalLat = lat + latMin + latSec + (0.5/24);
}
return { lat: finalLat, lon: finalLon };
}
export const usePOTASpots = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
@ -15,16 +38,53 @@ export const usePOTASpots = () => {
const res = await fetch('/api/pota/spots');
if (res.ok) {
const spots = await res.json();
setData(spots.slice(0, 10).map(s => ({
call: s.activator,
ref: s.reference,
freq: s.frequency,
mode: s.mode,
name: s.name || s.locationDesc,
lat: s.latitude,
lon: s.longitude,
time: s.spotTime ? new Date(s.spotTime).toISOString().substr(11,5)+'z' : ''
})));
// Filter out QRT spots and nearly-expired spots, then sort by most recent
const validSpots = spots
.filter(s => {
// Filter out QRT (operator signed off)
const comments = (s.comments || '').toUpperCase().trim();
if (comments === 'QRT' || comments.startsWith('QRT ') || comments.startsWith('QRT,')) return false;
// Filter out spots expiring within 60 seconds
if (typeof s.expire === 'number' && s.expire < 60) return false;
return true;
})
.sort((a, b) => {
// Sort by spotTime descending (newest first)
const timeA = a.spotTime ? new Date(a.spotTime).getTime() : 0;
const timeB = b.spotTime ? new Date(b.spotTime).getTime() : 0;
return timeB - timeA;
});
setData(validSpots.map(s => {
// Use API coordinates, fall back to grid square
let lat = s.latitude ? parseFloat(s.latitude) : null;
let lon = s.longitude ? parseFloat(s.longitude) : null;
if ((!lat || !lon) && s.grid6) {
const loc = gridToLatLon(s.grid6);
if (loc) { lat = loc.lat; lon = loc.lon; }
}
if ((!lat || !lon) && s.grid4) {
const loc = gridToLatLon(s.grid4);
if (loc) { lat = loc.lat; lon = loc.lon; }
}
return {
call: s.activator,
ref: s.reference,
freq: s.frequency,
mode: s.mode,
name: s.name || s.locationDesc,
locationDesc: s.locationDesc,
lat,
lon,
time: s.spotTime ? new Date(s.spotTime).toISOString().substr(11,5)+'z' : '',
expire: s.expire || 0
};
}));
}
} catch (err) {
console.error('POTA error:', err);
@ -34,7 +94,7 @@ export const usePOTASpots = () => {
};
fetchPOTA();
const interval = setInterval(fetchPOTA, 2 * 60 * 1000); // 2 minutes
const interval = setInterval(fetchPOTA, 60 * 1000); // 1 minute (was 2)
return () => clearInterval(interval);
}, []);

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

Loading…
Cancel
Save

Powered by TurnKey Linux.