Live ops wall + pluggable camera providers with licence gating (server-26#198)
Backend (c2-core):
- app/internal/cameras/: provider interface, licence model, per-provider
catalog cache; one adapter per source in design-canvas/camera-sources.md.
Implemented: 511NY (legacy shape + tolerant XML inventory parser, 511x
endpoints UNVERIFIED), Caltrans, DelDOT, Ontario 511, DriveBC, Calgary,
TfL, NSW, Fintraffic. Stubs: NYC DOT, Westchester DPW, TRANSCOM, CTroads,
511NJ, Austin, TxDOT, MD CHART, WSDOT, Tallinn, Tarktee, Warendorf,
TrafficLand, Windy, EarthCam.
- Every provider defaults OFF (config/camera_providers). Gated licences
(agreement required / unverified / non-commercial / paid) need an explicit
acknowledgement stored with who/when and written to audit_log; the audit
write is a precondition. Stubs and prohibited sources cannot be enabled.
Ack is bound to the licence status it was given for.
- GET /cameras (signed-in), GET/PUT /admin/camera-providers (admin only).
- /traffic/511 follows the ny511 provider switch and returns the DAA credit
+ NYSDOT as-is disclaimer; 511NY event descriptions no longer truncated
("may not alter the substance of a report").
Frontend:
- LiveView becomes the ops wall: Region > Incident > Camera drill-down,
worst-first rail (MapView), KPIs, transcript ticker, scrubber incident
bands, auto-tour, deep links (#inc=&cam=&tour), Esc steps up.
- Incident panel: nearest cameras (path-weighted), fused situational context
(ADS-B, AIS, 511NY events, NEXRAD tile sample), units, call spine.
- MapView keeps every layer; adds Camera Viewsheds (approx.) overlay and a
nearest-camera layer for the selected incident.
- CameraFeed hygiene: hidden-tab/off-screen gating, 2 concurrent HLS cap,
last good frame + stale badge, frozen-frame signature, HLS stall watchdog,
provider attribution rendered on the imagery.
- Camera/event popups show source credit, update time and the disclaimer.
- Admin > Camera providers tab with licence status, health, gated confirm.
- Camera detection is not built (blocked on #199/#200); seam only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d57738a8f6
commit
aa5b71d853
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import type { CameraCredit } from "@/lib/types";
|
||||
|
||||
/** "3 min ago (14:02)" for an epoch-seconds catalog/feed fetch time. */
|
||||
export function fetchedLabel(epochS: number | null | undefined): string | null {
|
||||
if (!epochS) return null;
|
||||
const ms = epochS * 1000;
|
||||
const m = Math.round((Date.now() - ms) / 60_000);
|
||||
const t = new Date(ms).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
||||
return m < 1 ? `just now (${t})` : `${m} min ago (${t})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source credit + freshness + pass-through disclaimer for third-party data
|
||||
* (511NY DAA: credit as a source credit only, "display the time at which the
|
||||
* data was published or last updated wherever you present it", and "best
|
||||
* efforts" to pass on NYSDOT's as-is disclaimer). Render it wherever that
|
||||
* provider's data or imagery is shown.
|
||||
*/
|
||||
export function SourceCredit({
|
||||
credit,
|
||||
updated,
|
||||
fallbackName,
|
||||
className,
|
||||
}: {
|
||||
credit?: CameraCredit | null;
|
||||
/** Human-readable "updated …" text; required by the DAA wherever 511NY data shows. */
|
||||
updated?: string | null;
|
||||
fallbackName?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const name = credit?.attribution ?? fallbackName ?? null;
|
||||
return (
|
||||
<div className={`text-[10px] text-ink-muted space-y-0.5 ${className ?? ""}`}>
|
||||
<div>
|
||||
{name && (credit?.attribution_url ? (
|
||||
<a href={credit.attribution_url} target="_blank" rel="noopener noreferrer" className="underline hover:text-ink-2">{name}</a>
|
||||
) : <span>{name}</span>)}
|
||||
{name && updated ? " · " : ""}
|
||||
{updated ? `updated ${updated}` : ""}
|
||||
</div>
|
||||
{credit?.disclaimer && (
|
||||
<details>
|
||||
<summary className="cursor-pointer select-none">Provided “as is”, without warranty — details</summary>
|
||||
<p className="mt-0.5 leading-snug whitespace-normal">{credit.disclaimer}</p>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user