feat(monitor/frontend): ISS panel (live position + next-pass placeholder for phase 6)
Four-row layout: Lat/Lon (degree symbol, mono), Alt (km, mono tabular), Vel (km/h with thousands separator, mono tabular), Next Pass (— stub). Reads snapshot.iss_position directly (no per-panel store — current position is the only thing the panel cares about, and the wheretheiss.at collector pushes a complete state every 10s, so latest-event semantics match what the panel needs). Real shape verified: latitude/longitude/ altitude/velocity/timestamp/fetched_at — all numeric except fetched_at ISO string. Stale threshold 30s (3x the 10s poll cadence). Next Pass row stays "—" muted --fg-3 — Phase 6 wires the SGP4-driven user-observer-location pass prediction per spec §10.4. No "sign in to add observer" CTA: operator UI doesn't onboard. Plan 5 Task 21 Design QA: degree symbol + mono coords, alt/vel mono tabular, no decorative ISS-tracing widget (the centerpiece globe owns that), next-pass row visible-but-muted (never hidden).
This commit is contained in:
parent
f0bb0dcb8d
commit
43df53f845
|
|
@ -6,6 +6,7 @@ import { BTCPanel } from '@/pages/panels/BTCPanel'
|
|||
import { CVEVelocityPanel } from '@/pages/panels/CVEVelocityPanel'
|
||||
import { DShieldPanel } from '@/pages/panels/DShieldPanel'
|
||||
import { ETHPanel } from '@/pages/panels/ETHPanel'
|
||||
import { ISSPanel } from '@/pages/panels/ISSPanel'
|
||||
import { KEVPanel } from '@/pages/panels/KEVPanel'
|
||||
import { RansomwarePanel } from '@/pages/panels/RansomwarePanel'
|
||||
import { SpaceWeatherPanel } from '@/pages/panels/SpaceWeatherPanel'
|
||||
|
|
@ -33,6 +34,7 @@ export function Dashboard(): React.ReactElement {
|
|||
<BTCPanel />
|
||||
<ETHPanel />
|
||||
<SpaceWeatherPanel />
|
||||
<ISSPanel />
|
||||
</aside>
|
||||
<section className={styles.center}>
|
||||
<Globe />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// ©AngelaMos | 2026
|
||||
// ISSPanel.module.scss
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 1fr;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
align-items: center;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--type-body);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--type-label);
|
||||
letter-spacing: var(--letter-spacing-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--fg-3);
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--fg-1);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--fg-3);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// ©AngelaMos | 2026
|
||||
// ISSPanel.tsx
|
||||
|
||||
import { useSnapshot } from '@/api/snapshot'
|
||||
import styles from './ISSPanel.module.scss'
|
||||
import { Panel } from './Panel'
|
||||
|
||||
const STALE_AFTER_MS = 30_000
|
||||
const COORDS_DECIMALS = 2
|
||||
const ALT_DECIMALS = 0
|
||||
|
||||
interface IssPositionData {
|
||||
latitude: number
|
||||
longitude: number
|
||||
altitude: number
|
||||
velocity: number
|
||||
timestamp: number
|
||||
fetched_at?: string
|
||||
}
|
||||
|
||||
export function ISSPanel(): React.ReactElement {
|
||||
const { data } = useSnapshot()
|
||||
const iss = data?.iss_position as IssPositionData | undefined
|
||||
|
||||
const lastTickAt = iss?.fetched_at
|
||||
? new Date(iss.fetched_at).getTime()
|
||||
: undefined
|
||||
const isStale =
|
||||
lastTickAt !== undefined
|
||||
? Date.now() - lastTickAt > STALE_AFTER_MS
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title="ISS"
|
||||
subtitle="POSITION"
|
||||
rawHref="https://wheretheiss.at/"
|
||||
rawLabel="wheretheiss.at"
|
||||
isStale={isStale}
|
||||
lastTickAt={lastTickAt}
|
||||
>
|
||||
<div className={styles.row}>
|
||||
<span className={styles.label}>Lat, Lon</span>
|
||||
<span className={styles.value}>
|
||||
{iss
|
||||
? `${iss.latitude.toFixed(COORDS_DECIMALS)}°, ${iss.longitude.toFixed(COORDS_DECIMALS)}°`
|
||||
: '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<span className={styles.label}>Alt</span>
|
||||
<span className={styles.value}>
|
||||
{iss ? `${iss.altitude.toFixed(ALT_DECIMALS)} km` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<span className={styles.label}>Vel</span>
|
||||
<span className={styles.value}>
|
||||
{iss ? `${Math.round(iss.velocity).toLocaleString()} km/h` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<span className={styles.label}>Next Pass</span>
|
||||
<span className={styles.muted}>—</span>
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
ISSPanel.displayName = 'ISSPanel'
|
||||
Loading…
Reference in New Issue