mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-18 08:27:18 +02:00
Add multi-airline ACARS summarizer for readable datalink dossiers.
Parse common position, OOOI, performance, and ops formats across major carriers while hiding VDL binary fragments and duplicate frames from the dossier feed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,11 +9,49 @@ type DatalinkMessage = {
|
||||
label?: string;
|
||||
text?: string;
|
||||
source_type?: string;
|
||||
summary?: string;
|
||||
kind?: string;
|
||||
readable?: boolean;
|
||||
};
|
||||
|
||||
const PRIORITY_POLL_MS = 3_000;
|
||||
const PRIORITY_POLL_MAX_MS = 45_000;
|
||||
|
||||
function DatalinkMessageRow({ message }: { message: DatalinkMessage }) {
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
const summary = message.summary?.trim();
|
||||
const raw = message.text?.trim() || '';
|
||||
const hasSummary = Boolean(summary);
|
||||
const showRawBlock = showRaw || (!hasSummary && raw);
|
||||
|
||||
return (
|
||||
<div className="text-[10px] font-mono leading-snug border border-[var(--border-primary)]/60 bg-black/20 px-2 py-1.5">
|
||||
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-0.5">
|
||||
<span>{formatDatalinkTime(message.timestamp)}</span>
|
||||
{message.label ? <span className="text-orange-400/90">{message.label}</span> : null}
|
||||
{message.source_type ? <span className="truncate">{message.source_type}</span> : null}
|
||||
</div>
|
||||
{hasSummary ? (
|
||||
<div className="text-[var(--text-primary)] break-words">{summary}</div>
|
||||
) : null}
|
||||
{hasSummary && raw ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowRaw((value) => !value)}
|
||||
className="mt-0.5 text-[var(--text-muted)] hover:text-cyan-400/90 underline underline-offset-2"
|
||||
>
|
||||
{showRaw ? 'hide raw' : 'show raw'}
|
||||
</button>
|
||||
) : null}
|
||||
{showRawBlock && raw ? (
|
||||
<div className="mt-0.5 text-[var(--text-muted)] whitespace-pre-wrap break-words text-[9px] leading-relaxed max-h-24 overflow-y-auto">
|
||||
{raw}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDatalinkTime(value?: string): string {
|
||||
if (!value) return '--:--';
|
||||
try {
|
||||
@@ -40,6 +78,8 @@ export default function DatalinkMessagesBlock({
|
||||
const [hint, setHint] = useState<string | null>(null);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [priorityScanning, setPriorityScanning] = useState(false);
|
||||
const [hiddenCount, setHiddenCount] = useState(0);
|
||||
const [showHidden, setShowHidden] = useState(false);
|
||||
const pollUntilRef = useRef(0);
|
||||
|
||||
const buildParams = useCallback(() => {
|
||||
@@ -71,6 +111,7 @@ export default function DatalinkMessagesBlock({
|
||||
const json = await res.json();
|
||||
setConfigured(Boolean(json.configured));
|
||||
setMessages(Array.isArray(json.messages) ? json.messages : []);
|
||||
setHiddenCount(typeof json.hidden_count === 'number' ? json.hidden_count : 0);
|
||||
setHint(typeof json.hint === 'string' ? json.hint : null);
|
||||
setLoadError(null);
|
||||
if (json.priority_scan || json.queued_refresh) {
|
||||
@@ -163,16 +204,27 @@ export default function DatalinkMessagesBlock({
|
||||
) : null}
|
||||
<div className="max-h-36 overflow-y-auto space-y-1.5 pr-1">
|
||||
{messages.map((message) => (
|
||||
<div key={message.id} className="text-[10px] font-mono leading-snug border border-[var(--border-primary)]/60 bg-black/20 px-2 py-1.5">
|
||||
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-0.5">
|
||||
<span>{formatDatalinkTime(message.timestamp)}</span>
|
||||
{message.label ? <span className="text-orange-400/90">{message.label}</span> : null}
|
||||
{message.source_type ? <span className="truncate">{message.source_type}</span> : null}
|
||||
</div>
|
||||
<div className="text-[var(--text-primary)] whitespace-pre-wrap break-words">{message.text}</div>
|
||||
</div>
|
||||
<DatalinkMessageRow key={message.id} message={message} />
|
||||
))}
|
||||
</div>
|
||||
{hiddenCount > 0 ? (
|
||||
<p className="text-[9px] font-mono text-[var(--text-muted)] mt-1">
|
||||
{hiddenCount} binary/fragment message{hiddenCount === 1 ? '' : 's'} hidden.{' '}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowHidden((value) => !value)}
|
||||
className="underline underline-offset-2 hover:text-cyan-400/90"
|
||||
>
|
||||
{showHidden ? 'Hide note' : 'Why?'}
|
||||
</button>
|
||||
{showHidden ? (
|
||||
<span className="block mt-0.5 text-[var(--text-muted)]/80">
|
||||
VDL splits long telemetry into many frames. Southwest also uses proprietary formats
|
||||
that cannot be decoded without airline keys.
|
||||
</span>
|
||||
) : null}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user