● Zion Boggan
repos/Sightline/frontend/src/app/satellites/page.tsx
zionboggan.com ↗
49 lines · tsx
History for this file →
1
"use client";
2
import { useEffect, useState } from "react";
3
import { api, SatelliteCam } from "@/lib/api";
4
 
5
export default function SatellitesPage() {
6
  const [sats, setSats] = useState<SatelliteCam[]>([]);
7
  const [err, setErr] = useState<string | null>(null);
8
  const [tick, setTick] = useState(0);
9
 
10
  function load() { api.satellites().then((r) => setSats(r.satellites)).catch((e) =>
11
    setErr(e.status === 401 ? "Sign in to view satellite cameras." : "Load failed.")); }
12
  useEffect(() => { load(); const t = setInterval(() => { load(); setTick((x) => x + 1); }, 15000); return () => clearInterval(t); }, []);
13
 
14
  const byConst: Record<string, number> = {};
15
  sats.forEach((s) => { const k = s.constellation || "other"; byConst[k] = (byConst[k] || 0) + 1; });
16
 
17
  return (
18
    <div>
19
      <h1>Satellite Cameras <span className="badge ok"><span className="live-dot" /> tracking</span></h1>
20
      <p className="sub">Real imaging / Earth-observation satellites (Planet SkySat &amp; Dove, Earth-resources).
21
        Positions propagated live via SGP4 - refreshes every 15s.</p>
22
 
23
      <div className="row" style={{ margin: "10px 0" }}>
24
        {Object.entries(byConst).map(([k, v]) => <span key={k} className="badge amber">{k}: {v}</span>)}
25
        <span className="muted">{sats.length} satellites tracked · update #{tick}</span>
26
      </div>
27
      {err && <div className="badge danger">{err}</div>}
28
 
29
      <div className="card scroll" style={{ maxHeight: "70vh" }}>
30
        <table>
31
          <thead><tr><th>Satellite</th><th>Constellation</th><th>Lat</th><th>Lon</th><th>Alt (km)</th><th>Period</th></tr></thead>
32
          <tbody>
33
            {sats.map((s) => (
34
              <tr key={s.identifier}>
35
                <td>🛰 {s.label}</td>
36
                <td><span className="badge amber">{s.constellation}</span></td>
37
                <td className="mono">{s.lat?.toFixed(2)}</td>
38
                <td className="mono">{s.lon?.toFixed(2)}</td>
39
                <td className="mono">{s.altitude_km}</td>
40
                <td className="muted">{s.period_min ? Math.round(s.period_min) + " min" : "-"}</td>
41
              </tr>
42
            ))}
43
            {!sats.length && !err && <tr><td colSpan={6} className="muted">No satellites loaded yet.</td></tr>}
44
          </tbody>
45
        </table>
46
      </div>
47
    </div>
48
  );
49
}