signalk-noaa-weather-report
Version:
Sends Voluntary Ship Observations to NOAA (and Windy) Requires an official ships radio callsign
285 lines (261 loc) • 9.72 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NOAA / Windy Ship Reporting Plugin</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}
h2 {
color: #34495e;
border-bottom: 2px solid #3498db;
padding-bottom: 5px;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-active {
background-color: #27ae60;
}
.status-inactive {
background-color: #e74c3c;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin: 15px 0;
}
.info-item {
background-color: #ecf0f1;
padding: 10px;
border-radius: 4px;
}
.info-label {
font-weight: bold;
color: #2c3e50;
}
.info-value {
color: #34495e;
margin-top: 5px;
}
.bbxx-report {
background-color: #2c3e50;
color: #ecf0f1;
padding: 15px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 14px;
word-break: break-all;
margin: 15px 0;
}
.human-readable {
background-color: #f8f9fa;
border-left: 4px solid #3498db;
padding: 15px;
margin: 15px 0;
white-space: pre-line;
font-family: 'Courier New', monospace;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin: 10px 5px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
}
.loading {
display: none;
color: #3498db;
font-style: italic;
}
.error {
color: #e74c3c;
background-color: #fadbd8;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success {
color: #27ae60;
background-color: #d5f4e6;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.timestamp {
color: #7f8c8d;
font-size: 0.9em;
}
</style>
</head>
<body>
<h1>🌊 NOAA / Windy Ship Reporting Plugin</h1>
<div class="container">
<h2>Plugin Status</h2>
<div id="plugin-status">
<span class="status-indicator" id="status-indicator"></span>
<span id="status-text">Loading...</span>
</div>
</div>
<div class="container">
<h2>Actions</h2>
<button id="refresh-btn" onclick="refreshStatus()">🔄 Refresh Status</button>
<button id="generate-btn" onclick="generateReport()">📊 Generate Report</button>
<div id="loading" class="loading">Generating report...</div>
<div id="message"></div>
</div>
<div class="container" id="last-report" style="display: none;">
<h2>Last Report</h2>
<div class="timestamp" id="report-timestamp"></div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Position</div>
<div class="info-value" id="position"></div>
</div>
<div class="info-item">
<div class="info-label">True Wind Direction</div>
<div class="info-value" id="wind-direction"></div>
</div>
<div class="info-item">
<div class="info-label">True Wind Speed</div>
<div class="info-value" id="wind-speed"></div>
</div>
<div class="info-item">
<div class="info-label">Report Generated</div>
<div class="info-value" id="report-time"></div>
</div>
</div>
<h3>Human Readable Report</h3>
<div class="human-readable" id="human-readable"></div>
<h3>BBXX Report</h3>
<div class="bbxx-report" id="bbxx-report"></div>
</div>
<script>
let statusData = null;
async function refreshStatus() {
try {
const response = await fetch('/plugins/signalk-noaa-weather-report/status');
statusData = await response.json();
updateStatusDisplay();
} catch (error) {
console.error('Error fetching status:', error);
showMessage('Error fetching plugin status', 'error');
}
}
function updateStatusDisplay() {
const statusIndicator = document.getElementById('status-indicator');
const statusText = document.getElementById('status-text');
const lastReportDiv = document.getElementById('last-report');
const generateBtn = document.getElementById('generate-btn');
if (statusData.pluginStarted) {
statusIndicator.className = 'status-indicator status-active';
statusText.textContent = 'Plugin Active';
generateBtn.disabled = false;
} else {
statusIndicator.className = 'status-indicator status-inactive';
statusText.textContent = 'Plugin Inactive';
generateBtn.disabled = true;
}
if (statusData.lastReport) {
lastReportDiv.style.display = 'block';
updateLastReportDisplay(statusData.lastReport);
} else {
lastReportDiv.style.display = 'none';
}
}
function updateLastReportDisplay(report) {
document.getElementById('report-timestamp').textContent =
`Generated: ${new Date(report.timestamp).toLocaleString()}`;
document.getElementById('position').textContent =
`${report.position.lat.toFixed(6)}, ${report.position.lon.toFixed(6)}`;
document.getElementById('wind-direction').textContent =
`${report.trueWind.direction.toFixed(1)}°`;
document.getElementById('wind-speed').textContent =
`${report.trueWind.speed.toFixed(2)} knots`;
document.getElementById('report-time').textContent =
new Date(report.timestamp).toLocaleTimeString();
document.getElementById('human-readable').textContent = report.humanReadable;
document.getElementById('bbxx-report').textContent = report.bbxx;
}
async function generateReport() {
const generateBtn = document.getElementById('generate-btn');
const loading = document.getElementById('loading');
const messageDiv = document.getElementById('message');
generateBtn.disabled = true;
loading.style.display = 'block';
messageDiv.innerHTML = '';
try {
const response = await fetch('/plugins/signalk-noaa-weather-report/generate-report', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (result.success && result.report) {
showMessage('Report generated successfully!', 'success');
// Update the display with the new report
statusData.lastReport = result.report;
updateLastReportDisplay(result.report);
document.getElementById('last-report').style.display = 'block';
} else {
showMessage('Failed to generate report', 'error');
}
} catch (error) {
console.error('Error generating report:', error);
showMessage('Error generating report: ' + error.message, 'error');
} finally {
generateBtn.disabled = false;
loading.style.display = 'none';
}
}
function showMessage(text, type) {
const messageDiv = document.getElementById('message');
messageDiv.innerHTML = `<div class="${type}">${text}</div>`;
setTimeout(() => {
messageDiv.innerHTML = '';
}, 5000);
}
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
refreshStatus();
// Auto-refresh every 30 seconds
setInterval(refreshStatus, 30000);
});
</script>
</body>
</html>