mirror of
https://github.com/smittix/intercept.git
synced 2026-06-10 15:03:31 -07:00
Add APRS function bar similar to ADS-B stats strip
Move SDR configuration controls from sidebar to a horizontal function bar above the map display for better visibility and accessibility. The bar includes frequency/station/packet stats, region and gain controls, tool status indicators, and start/stop buttons. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+198
-1
@@ -1,4 +1,201 @@
|
||||
/* APRS Status Bar Styles */
|
||||
/* APRS Function Bar (Stats Strip) Styles */
|
||||
.aprs-strip {
|
||||
background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
margin-bottom: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.aprs-strip-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: max-content;
|
||||
}
|
||||
.aprs-strip .strip-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
background: rgba(74, 158, 255, 0.05);
|
||||
border: 1px solid rgba(74, 158, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
min-width: 55px;
|
||||
}
|
||||
.aprs-strip .strip-stat:hover {
|
||||
background: rgba(74, 158, 255, 0.1);
|
||||
border-color: rgba(74, 158, 255, 0.3);
|
||||
}
|
||||
.aprs-strip .strip-value {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-cyan);
|
||||
line-height: 1.2;
|
||||
}
|
||||
.aprs-strip .strip-label {
|
||||
font-size: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.aprs-strip .strip-divider {
|
||||
width: 1px;
|
||||
height: 28px;
|
||||
background: var(--border-color);
|
||||
margin: 0 4px;
|
||||
}
|
||||
/* Signal stat coloring */
|
||||
.aprs-strip .signal-stat.good .strip-value { color: var(--accent-green); }
|
||||
.aprs-strip .signal-stat.warning .strip-value { color: var(--accent-yellow); }
|
||||
.aprs-strip .signal-stat.poor .strip-value { color: var(--accent-red); }
|
||||
|
||||
/* Controls */
|
||||
.aprs-strip .strip-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.aprs-strip .strip-select {
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.aprs-strip .strip-select:hover {
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
.aprs-strip .strip-input-label {
|
||||
font-size: 9px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
.aprs-strip .strip-input {
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
.aprs-strip .strip-input:hover,
|
||||
.aprs-strip .strip-input:focus {
|
||||
border-color: var(--accent-cyan);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Tool Status Indicators */
|
||||
.aprs-strip .strip-tools {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.aprs-strip .strip-tool {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 59, 48, 0.2);
|
||||
color: var(--accent-red);
|
||||
border: 1px solid rgba(255, 59, 48, 0.3);
|
||||
}
|
||||
.aprs-strip .strip-tool.ok {
|
||||
background: rgba(0, 255, 136, 0.1);
|
||||
color: var(--accent-green);
|
||||
border-color: rgba(0, 255, 136, 0.3);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.aprs-strip .strip-btn {
|
||||
background: rgba(74, 158, 255, 0.1);
|
||||
border: 1px solid rgba(74, 158, 255, 0.2);
|
||||
color: var(--text-primary);
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.aprs-strip .strip-btn:hover:not(:disabled) {
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
border-color: rgba(74, 158, 255, 0.4);
|
||||
}
|
||||
.aprs-strip .strip-btn.primary {
|
||||
background: linear-gradient(135deg, var(--accent-green) 0%, #10b981 100%);
|
||||
border: none;
|
||||
color: #000;
|
||||
}
|
||||
.aprs-strip .strip-btn.primary:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.aprs-strip .strip-btn.stop {
|
||||
background: linear-gradient(135deg, var(--accent-red) 0%, #dc2626 100%);
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
.aprs-strip .strip-btn.stop:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.aprs-strip .strip-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Status indicator */
|
||||
.aprs-strip .strip-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.aprs-strip .status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-muted);
|
||||
}
|
||||
.aprs-strip .status-dot.listening {
|
||||
background: var(--accent-cyan);
|
||||
animation: aprs-strip-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
.aprs-strip .status-dot.tracking {
|
||||
background: var(--accent-green);
|
||||
animation: aprs-strip-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
.aprs-strip .status-dot.error {
|
||||
background: var(--accent-red);
|
||||
}
|
||||
@keyframes aprs-strip-pulse {
|
||||
0%, 100% { opacity: 1; box-shadow: 0 0 4px 2px currentColor; }
|
||||
50% { opacity: 0.6; box-shadow: none; }
|
||||
}
|
||||
|
||||
/* Time display */
|
||||
.aprs-strip .strip-time {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
padding: 4px 8px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* APRS Status Bar Styles (Sidebar - legacy) */
|
||||
.aprs-status-bar {
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
|
||||
+170
-110
@@ -726,6 +726,63 @@
|
||||
|
||||
<!-- APRS Visualizations -->
|
||||
<div id="aprsVisuals" style="display: none; flex-direction: column; gap: 10px; flex: 1; padding: 10px;">
|
||||
<!-- APRS Function Bar -->
|
||||
<div class="aprs-strip">
|
||||
<div class="aprs-strip-inner">
|
||||
<!-- Stats -->
|
||||
<div class="strip-stat">
|
||||
<span class="strip-value" id="aprsStripFreq">--</span>
|
||||
<span class="strip-label">MHz</span>
|
||||
</div>
|
||||
<div class="strip-stat">
|
||||
<span class="strip-value" id="aprsStripStations">0</span>
|
||||
<span class="strip-label">STATIONS</span>
|
||||
</div>
|
||||
<div class="strip-stat">
|
||||
<span class="strip-value" id="aprsStripPackets">0</span>
|
||||
<span class="strip-label">PACKETS</span>
|
||||
</div>
|
||||
<div class="strip-stat signal-stat" id="aprsStripSignalStat">
|
||||
<span class="strip-value" id="aprsStripSignal">--</span>
|
||||
<span class="strip-label">SIGNAL</span>
|
||||
</div>
|
||||
<div class="strip-divider"></div>
|
||||
<!-- Controls -->
|
||||
<div class="strip-control">
|
||||
<select id="aprsStripRegion" class="strip-select">
|
||||
<option value="north_america">N. America (144.390)</option>
|
||||
<option value="europe">Europe (144.800)</option>
|
||||
<option value="uk">UK (144.800)</option>
|
||||
<option value="australia">Australia (145.175)</option>
|
||||
<option value="japan">Japan (144.640)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="strip-control">
|
||||
<span class="strip-input-label">GAIN</span>
|
||||
<input type="number" id="aprsStripGain" class="strip-input" value="40" min="0" max="50">
|
||||
</div>
|
||||
<div class="strip-divider"></div>
|
||||
<!-- Tool Status Indicators -->
|
||||
<div class="strip-tools">
|
||||
<span class="strip-tool" id="aprsStripDirewolf" title="direwolf">DW</span>
|
||||
<span class="strip-tool" id="aprsStripMultimon" title="multimon-ng">MM</span>
|
||||
</div>
|
||||
<div class="strip-divider"></div>
|
||||
<!-- Actions -->
|
||||
<button type="button" class="strip-btn primary" id="aprsStripStartBtn" onclick="startAprs()">
|
||||
▶ START
|
||||
</button>
|
||||
<button type="button" class="strip-btn stop" id="aprsStripStopBtn" onclick="stopAprs()" style="display: none;">
|
||||
◼ STOP
|
||||
</button>
|
||||
<!-- Status -->
|
||||
<div class="strip-status">
|
||||
<div class="status-dot inactive" id="aprsStripDot"></div>
|
||||
<span id="aprsStripStatus">STANDBY</span>
|
||||
</div>
|
||||
<div class="strip-time" id="aprsStripTime">--:--:-- UTC</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Top row: Map and Station List side by side -->
|
||||
<div style="display: flex; gap: 10px; flex: 1; min-height: 0;">
|
||||
<!-- Map Panel (larger) -->
|
||||
@@ -5958,28 +6015,29 @@
|
||||
fetch('/aprs/tools')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const direwolfStatus = document.getElementById('direwolfStatus');
|
||||
const multimonStatus = document.getElementById('aprsMultimonStatus');
|
||||
// Update function bar tool indicators
|
||||
const direwolfEl = document.getElementById('aprsStripDirewolf');
|
||||
const multimonEl = document.getElementById('aprsStripMultimon');
|
||||
|
||||
if (direwolfStatus) {
|
||||
direwolfStatus.textContent = data.direwolf ? 'OK' : 'Missing';
|
||||
direwolfStatus.className = 'tool-status ' + (data.direwolf ? 'ok' : 'missing');
|
||||
if (direwolfEl) {
|
||||
direwolfEl.className = 'strip-tool' + (data.direwolf ? ' ok' : '');
|
||||
direwolfEl.title = 'direwolf: ' + (data.direwolf ? 'OK' : 'Missing');
|
||||
}
|
||||
if (multimonStatus) {
|
||||
multimonStatus.textContent = data.multimon_ng ? 'OK' : 'Missing';
|
||||
multimonStatus.className = 'tool-status ' + (data.multimon_ng ? 'ok' : 'missing');
|
||||
if (multimonEl) {
|
||||
multimonEl.className = 'strip-tool' + (data.multimon_ng ? ' ok' : '');
|
||||
multimonEl.title = 'multimon-ng: ' + (data.multimon_ng ? 'OK' : 'Missing');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
const direwolfStatus = document.getElementById('direwolfStatus');
|
||||
const multimonStatus = document.getElementById('aprsMultimonStatus');
|
||||
if (direwolfStatus) {
|
||||
direwolfStatus.textContent = 'Error';
|
||||
direwolfStatus.className = 'tool-status missing';
|
||||
const direwolfEl = document.getElementById('aprsStripDirewolf');
|
||||
const multimonEl = document.getElementById('aprsStripMultimon');
|
||||
if (direwolfEl) {
|
||||
direwolfEl.className = 'strip-tool';
|
||||
direwolfEl.title = 'direwolf: Error';
|
||||
}
|
||||
if (multimonStatus) {
|
||||
multimonStatus.textContent = 'Error';
|
||||
multimonStatus.className = 'tool-status missing';
|
||||
if (multimonEl) {
|
||||
multimonEl.className = 'strip-tool';
|
||||
multimonEl.title = 'multimon-ng: Error';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5997,45 +6055,51 @@
|
||||
maxZoom: 19
|
||||
}).addTo(aprsMap);
|
||||
|
||||
// Update time display
|
||||
// Update time display (both map header and function bar)
|
||||
setInterval(() => {
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString('en-US', {hour12: false});
|
||||
const utcStr = now.toUTCString().slice(17, 25) + ' UTC';
|
||||
|
||||
const timeEl = document.getElementById('aprsMapTime');
|
||||
if (timeEl) {
|
||||
timeEl.textContent = new Date().toLocaleTimeString('en-US', {hour12: false});
|
||||
}
|
||||
if (timeEl) timeEl.textContent = timeStr;
|
||||
|
||||
const stripTimeEl = document.getElementById('aprsStripTime');
|
||||
if (stripTimeEl) stripTimeEl.textContent = utcStr;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function updateAprsStatus(state, freq) {
|
||||
const statusBar = document.getElementById('aprsStatusBar');
|
||||
const statusDot = document.getElementById('aprsStatusDot');
|
||||
const statusText = document.getElementById('aprsStatusText');
|
||||
const freqEl = document.getElementById('aprsStatusFreq');
|
||||
// Update function bar status
|
||||
const stripDot = document.getElementById('aprsStripDot');
|
||||
const stripStatus = document.getElementById('aprsStripStatus');
|
||||
const stripFreq = document.getElementById('aprsStripFreq');
|
||||
|
||||
statusBar.style.display = 'block';
|
||||
statusDot.className = 'aprs-status-dot ' + state;
|
||||
statusText.textContent = state.toUpperCase();
|
||||
|
||||
if (freq) {
|
||||
freqEl.textContent = freq + ' MHz';
|
||||
if (stripDot) {
|
||||
stripDot.className = 'status-dot ' + state;
|
||||
}
|
||||
|
||||
// Update color based on state
|
||||
if (state === 'listening') {
|
||||
statusText.style.color = 'var(--accent-cyan)';
|
||||
} else if (state === 'tracking') {
|
||||
statusText.style.color = 'var(--accent-green)';
|
||||
} else if (state === 'error') {
|
||||
statusText.style.color = 'var(--accent-red)';
|
||||
} else {
|
||||
statusText.style.color = 'var(--text-muted)';
|
||||
if (stripStatus) {
|
||||
stripStatus.textContent = state.toUpperCase();
|
||||
if (state === 'listening') {
|
||||
stripStatus.style.color = 'var(--accent-cyan)';
|
||||
} else if (state === 'tracking') {
|
||||
stripStatus.style.color = 'var(--accent-green)';
|
||||
} else if (state === 'error') {
|
||||
stripStatus.style.color = 'var(--accent-red)';
|
||||
} else {
|
||||
stripStatus.style.color = '';
|
||||
}
|
||||
}
|
||||
if (freq && stripFreq) {
|
||||
stripFreq.textContent = freq;
|
||||
}
|
||||
}
|
||||
|
||||
function startAprs() {
|
||||
const region = document.getElementById('aprsRegion').value;
|
||||
// Get values from function bar controls
|
||||
const region = document.getElementById('aprsStripRegion').value;
|
||||
const device = getSelectedDevice();
|
||||
const gain = document.getElementById('aprsGain').value;
|
||||
const gain = document.getElementById('aprsStripGain').value;
|
||||
|
||||
fetch('/aprs/start', {
|
||||
method: 'POST',
|
||||
@@ -6048,17 +6112,21 @@
|
||||
isAprsRunning = true;
|
||||
aprsPacketCount = 0;
|
||||
aprsStationCount = 0;
|
||||
document.getElementById('startAprsBtn').style.display = 'none';
|
||||
document.getElementById('stopAprsBtn').style.display = 'block';
|
||||
// Update function bar buttons
|
||||
document.getElementById('aprsStripStartBtn').style.display = 'none';
|
||||
document.getElementById('aprsStripStopBtn').style.display = 'inline-block';
|
||||
// Update map status
|
||||
document.getElementById('aprsMapStatus').textContent = 'TRACKING';
|
||||
document.getElementById('aprsMapStatus').style.color = 'var(--accent-green)';
|
||||
// Update sidebar status bar
|
||||
// Update function bar status
|
||||
updateAprsStatus('listening', data.frequency);
|
||||
document.getElementById('aprsStatusStations').textContent = '0';
|
||||
document.getElementById('aprsStatusPackets').textContent = '0';
|
||||
// Show signal meter
|
||||
document.getElementById('aprsSignalMeter').style.display = 'block';
|
||||
resetAprsMeter();
|
||||
// Reset function bar stats
|
||||
document.getElementById('aprsStripStations').textContent = '0';
|
||||
document.getElementById('aprsStripPackets').textContent = '0';
|
||||
document.getElementById('aprsStripSignal').textContent = '--';
|
||||
// Disable controls while running
|
||||
document.getElementById('aprsStripRegion').disabled = true;
|
||||
document.getElementById('aprsStripGain').disabled = true;
|
||||
startAprsMeterCheck();
|
||||
startAprsStream();
|
||||
} else {
|
||||
@@ -6077,13 +6145,24 @@
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
isAprsRunning = false;
|
||||
document.getElementById('startAprsBtn').style.display = 'block';
|
||||
document.getElementById('stopAprsBtn').style.display = 'none';
|
||||
// Hide sidebar status bar and signal meter
|
||||
document.getElementById('aprsStatusBar').style.display = 'none';
|
||||
document.getElementById('aprsSignalMeter').style.display = 'none';
|
||||
// Update function bar buttons
|
||||
document.getElementById('aprsStripStartBtn').style.display = 'inline-block';
|
||||
document.getElementById('aprsStripStopBtn').style.display = 'none';
|
||||
// Update map status
|
||||
document.getElementById('aprsMapStatus').textContent = 'STANDBY';
|
||||
document.getElementById('aprsMapStatus').style.color = '';
|
||||
// Reset function bar status
|
||||
updateAprsStatus('standby');
|
||||
document.getElementById('aprsStripFreq').textContent = '--';
|
||||
document.getElementById('aprsStripSignal').textContent = '--';
|
||||
// Re-enable controls
|
||||
document.getElementById('aprsStripRegion').disabled = false;
|
||||
document.getElementById('aprsStripGain').disabled = false;
|
||||
// Remove signal quality class
|
||||
const signalStat = document.getElementById('aprsStripSignalStat');
|
||||
if (signalStat) {
|
||||
signalStat.classList.remove('good', 'warning', 'poor');
|
||||
}
|
||||
// Stop meter check interval
|
||||
stopAprsMeterCheck();
|
||||
if (aprsEventSource) {
|
||||
@@ -6101,17 +6180,17 @@
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'aprs') {
|
||||
aprsPacketCount++;
|
||||
// Update map footer and function bar
|
||||
document.getElementById('aprsPacketCount').textContent = aprsPacketCount;
|
||||
// Update sidebar status bar
|
||||
document.getElementById('aprsStatusPackets').textContent = aprsPacketCount;
|
||||
document.getElementById('aprsStripPackets').textContent = aprsPacketCount;
|
||||
// Switch to tracking state on first packet
|
||||
const dot = document.getElementById('aprsStatusDot');
|
||||
const dot = document.getElementById('aprsStripDot');
|
||||
if (dot && !dot.classList.contains('tracking')) {
|
||||
updateAprsStatus('tracking');
|
||||
}
|
||||
processAprsPacket(data);
|
||||
} else if (data.type === 'meter') {
|
||||
// Update signal meter
|
||||
// Update signal indicator in function bar
|
||||
updateAprsMeter(data.level);
|
||||
}
|
||||
};
|
||||
@@ -6125,59 +6204,41 @@
|
||||
// Signal Meter Functions
|
||||
function resetAprsMeter() {
|
||||
aprsMeterLastUpdate = 0;
|
||||
const bar = document.getElementById('aprsMeterBar');
|
||||
const value = document.getElementById('aprsMeterValue');
|
||||
const burst = document.getElementById('aprsMeterBurst');
|
||||
const status = document.getElementById('aprsMeterStatus');
|
||||
if (bar) {
|
||||
bar.style.width = '0%';
|
||||
bar.classList.remove('no-signal');
|
||||
}
|
||||
if (value) value.textContent = '--';
|
||||
if (burst) burst.style.display = 'none';
|
||||
if (status) {
|
||||
status.textContent = 'Waiting for signal...';
|
||||
status.className = 'aprs-meter-status';
|
||||
}
|
||||
// Reset function bar signal indicator
|
||||
const signalEl = document.getElementById('aprsStripSignal');
|
||||
const signalStat = document.getElementById('aprsStripSignalStat');
|
||||
if (signalEl) signalEl.textContent = '--';
|
||||
if (signalStat) signalStat.classList.remove('good', 'warning', 'poor');
|
||||
}
|
||||
|
||||
function updateAprsMeter(level) {
|
||||
aprsMeterLastUpdate = Date.now();
|
||||
const bar = document.getElementById('aprsMeterBar');
|
||||
const value = document.getElementById('aprsMeterValue');
|
||||
const burst = document.getElementById('aprsMeterBurst');
|
||||
const status = document.getElementById('aprsMeterStatus');
|
||||
|
||||
if (bar) {
|
||||
bar.style.width = level + '%';
|
||||
bar.classList.remove('no-signal');
|
||||
}
|
||||
if (value) value.textContent = level;
|
||||
// Update function bar signal indicator
|
||||
const signalEl = document.getElementById('aprsStripSignal');
|
||||
const signalStat = document.getElementById('aprsStripSignalStat');
|
||||
|
||||
// Show burst indicator for high levels (>70)
|
||||
if (burst) {
|
||||
if (level > 70) {
|
||||
burst.style.display = 'inline';
|
||||
// Remove and re-add to trigger animation
|
||||
burst.style.animation = 'none';
|
||||
burst.offsetHeight; // Trigger reflow
|
||||
burst.style.animation = null;
|
||||
if (signalEl) {
|
||||
// Show signal level as bars
|
||||
if (level >= 60) {
|
||||
signalEl.textContent = '●●●';
|
||||
} else if (level >= 30) {
|
||||
signalEl.textContent = '●●○';
|
||||
} else if (level >= 10) {
|
||||
signalEl.textContent = '●○○';
|
||||
} else {
|
||||
burst.style.display = 'none';
|
||||
signalEl.textContent = '○○○';
|
||||
}
|
||||
}
|
||||
|
||||
// Update status text
|
||||
if (status) {
|
||||
status.className = 'aprs-meter-status active';
|
||||
if (level < 10) {
|
||||
status.textContent = 'Low signal / noise floor';
|
||||
} else if (level < 30) {
|
||||
status.textContent = 'Weak signal detected';
|
||||
} else if (level < 60) {
|
||||
status.textContent = 'Moderate signal';
|
||||
if (signalStat) {
|
||||
signalStat.classList.remove('good', 'warning', 'poor');
|
||||
if (level >= 60) {
|
||||
signalStat.classList.add('good');
|
||||
} else if (level >= 30) {
|
||||
signalStat.classList.add('warning');
|
||||
} else {
|
||||
status.textContent = 'Strong signal / packet burst';
|
||||
signalStat.classList.add('poor');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6187,14 +6248,12 @@
|
||||
aprsMeterCheckInterval = setInterval(function() {
|
||||
if (aprsMeterLastUpdate > 0 && (Date.now() - aprsMeterLastUpdate) > APRS_METER_TIMEOUT) {
|
||||
// No meter updates for 5 seconds - show no-signal state
|
||||
const bar = document.getElementById('aprsMeterBar');
|
||||
const status = document.getElementById('aprsMeterStatus');
|
||||
const burst = document.getElementById('aprsMeterBurst');
|
||||
if (bar) bar.classList.add('no-signal');
|
||||
if (burst) burst.style.display = 'none';
|
||||
if (status) {
|
||||
status.textContent = 'No RF activity / silence';
|
||||
status.className = 'aprs-meter-status no-signal';
|
||||
const signalEl = document.getElementById('aprsStripSignal');
|
||||
const signalStat = document.getElementById('aprsStripSignalStat');
|
||||
if (signalEl) signalEl.textContent = '○○○';
|
||||
if (signalStat) {
|
||||
signalStat.classList.remove('good', 'warning');
|
||||
signalStat.classList.add('poor');
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
@@ -6250,8 +6309,9 @@
|
||||
} else {
|
||||
// Create new marker
|
||||
aprsStationCount++;
|
||||
// Update map footer and function bar
|
||||
document.getElementById('aprsStationCount').textContent = aprsStationCount;
|
||||
document.getElementById('aprsStatusStations').textContent = aprsStationCount;
|
||||
document.getElementById('aprsStripStations').textContent = aprsStationCount;
|
||||
|
||||
const icon = L.divIcon({
|
||||
className: 'aprs-marker',
|
||||
|
||||
@@ -9,63 +9,8 @@
|
||||
<strong style="color: var(--accent-yellow);">Amateur Radio</strong><br>
|
||||
<span style="color: var(--text-secondary);">APRS operates on 144.390 MHz (N. America) or 144.800 MHz (Europe). Decodes position, weather, and messages from ham radio operators.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>Configuration</h3>
|
||||
<div class="form-group">
|
||||
<label>Region</label>
|
||||
<select id="aprsRegion">
|
||||
<option value="north_america">North America (144.390)</option>
|
||||
<option value="europe">Europe (144.800)</option>
|
||||
<option value="uk">UK (144.800)</option>
|
||||
<option value="australia">Australia (145.175)</option>
|
||||
<option value="japan">Japan (144.640)</option>
|
||||
</select>
|
||||
<div style="background: rgba(74, 158, 255, 0.1); border: 1px solid rgba(74, 158, 255, 0.3); border-radius: 4px; padding: 8px; font-size: 10px;">
|
||||
<span style="color: var(--accent-cyan);">Controls in function bar above map</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Gain (dB)</label>
|
||||
<input type="text" id="aprsGain" value="40" placeholder="40">
|
||||
</div>
|
||||
<div class="info-text" style="margin-top: 8px; display: grid; grid-template-columns: auto auto; gap: 4px 8px; align-items: center;">
|
||||
<span>direwolf:</span><span class="tool-status" id="direwolfStatus">Checking...</span>
|
||||
<span>multimon-ng:</span><span class="tool-status" id="aprsMultimonStatus">Checking...</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="run-btn" id="startAprsBtn" onclick="startAprs()">
|
||||
Start APRS
|
||||
</button>
|
||||
<button class="stop-btn" id="stopAprsBtn" onclick="stopAprs()" style="display: none;">
|
||||
Stop APRS
|
||||
</button>
|
||||
<!-- APRS Status Bar -->
|
||||
<div id="aprsStatusBar" class="aprs-status-bar" style="display: none;">
|
||||
<div class="aprs-status-indicator">
|
||||
<span class="aprs-status-dot" id="aprsStatusDot"></span>
|
||||
<span class="aprs-status-text" id="aprsStatusText">STANDBY</span>
|
||||
</div>
|
||||
<div class="aprs-status-stats">
|
||||
<span class="aprs-stat"><span class="aprs-stat-label">FREQ:</span> <span id="aprsStatusFreq">--</span></span>
|
||||
<span class="aprs-stat"><span class="aprs-stat-label">STATIONS:</span> <span id="aprsStatusStations">0</span></span>
|
||||
<span class="aprs-stat"><span class="aprs-stat-label">PACKETS:</span> <span id="aprsStatusPackets">0</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Signal Meter -->
|
||||
<div id="aprsSignalMeter" class="aprs-signal-meter" style="display: none;">
|
||||
<div class="aprs-meter-header">
|
||||
<span class="aprs-meter-label">SIGNAL</span>
|
||||
<span class="aprs-meter-value" id="aprsMeterValue">--</span>
|
||||
<span class="aprs-meter-burst" id="aprsMeterBurst" style="display: none;">BURST</span>
|
||||
</div>
|
||||
<div class="aprs-meter-bar-container">
|
||||
<div class="aprs-meter-bar" id="aprsMeterBar"></div>
|
||||
<div class="aprs-meter-ticks">
|
||||
<span>0</span>
|
||||
<span>25</span>
|
||||
<span>50</span>
|
||||
<span>75</span>
|
||||
<span>100</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aprs-meter-status" id="aprsMeterStatus">Waiting for signal...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user