Add chart.js dashboard

This commit is contained in:
2025-10-24 10:47:33 -04:00
parent 59326ecf7e
commit 321fd4a372

114
main.go
View File

@ -64,11 +64,9 @@ func estimateCloudCover(url string) (float64, error) {
defer valMask.Close()
defer cloudMask.Close()
// s < 60 (low saturation)
// Low saturation, high brightness = likely clouds
gocv.InRangeWithScalar(s, gocv.NewScalar(0, 0, 0, 0), gocv.NewScalar(60, 0, 0, 0), &satMask)
// v > 120 (bright)
gocv.InRangeWithScalar(v, gocv.NewScalar(120, 0, 0, 0), gocv.NewScalar(255, 0, 0, 0), &valMask)
gocv.BitwiseAnd(satMask, valMask, &cloudMask)
totalPixels := cloudMask.Rows() * cloudMask.Cols()
@ -92,9 +90,8 @@ func backgroundCollector() {
trendLock.Lock()
trendHistory = append(trendHistory, result)
// Keep last 100 entries
if len(trendHistory) > 100 {
trendHistory = trendHistory[len(trendHistory)-100:]
if len(trendHistory) > 200 {
trendHistory = trendHistory[len(trendHistory)-200:]
}
trendLock.Unlock()
@ -122,28 +119,93 @@ func getEnvInt(key string, def int) int {
func main() {
go backgroundCollector()
http.HandleFunc("/cloudcover", func(w http.ResponseWriter, r *http.Request) {
trendLock.Lock()
defer trendLock.Unlock()
if len(trendHistory) == 0 {
http.Error(w, "No data collected yet", http.StatusServiceUnavailable)
return
}
latest := trendHistory[len(trendHistory)-1]
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(latest)
})
http.HandleFunc("/cloudtrend", func(w http.ResponseWriter, r *http.Request) {
trendLock.Lock()
defer trendLock.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(trendHistory)
})
http.HandleFunc("/", dashboardHandler)
http.HandleFunc("/cloudcover", handleLatest)
http.HandleFunc("/cloudtrend", handleTrend)
fmt.Printf("☁️ Cloud cover API started at http://localhost:8080\n")
fmt.Printf("🔁 Refresh interval: %ds | Source: %s\n", refreshSecs, imageURL)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleLatest(w http.ResponseWriter, r *http.Request) {
trendLock.Lock()
defer trendLock.Unlock()
if len(trendHistory) == 0 {
http.Error(w, "No data collected yet", http.StatusServiceUnavailable)
return
}
latest := trendHistory[len(trendHistory)-1]
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(latest)
}
func handleTrend(w http.ResponseWriter, r *http.Request) {
trendLock.Lock()
defer trendLock.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(trendHistory)
}
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>☁️ Cloud Cover Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: sans-serif; background: #111; color: #eee; text-align: center; margin: 0; padding: 1em; }
canvas { background: #222; border-radius: 10px; margin-top: 20px; max-width: 90vw; }
</style>
</head>
<body>
<h1>☁️ Cloud Cover Trend</h1>
<p>Live view of sky cloudiness from <code>` + imageURL + `</code></p>
<canvas id="chart" width="800" height="400"></canvas>
<script>
const ctx = document.getElementById('chart');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Cloud Cover (%)',
data: [],
borderColor: '#4fc3f7',
backgroundColor: 'rgba(79,195,247,0.2)',
fill: true,
tension: 0.3
}]
},
options: {
scales: {
x: { title: { display: true, text: 'Time' }, ticks: { color: '#aaa' } },
y: { beginAtZero: true, title: { display: true, text: 'Cloud %' }, ticks: { color: '#aaa' } }
},
plugins: {
legend: { labels: { color: '#ccc' } }
}
}
});
async function fetchData() {
const res = await fetch('/cloudtrend');
const data = await res.json();
chart.data.labels = data.map(d => new Date(d.timestamp).toLocaleTimeString());
chart.data.datasets[0].data = data.map(d => d.cloud_cover);
chart.update();
}
fetchData();
setInterval(fetchData, 10000);
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(html))
}