"use client"; /** * Admin → Camera providers. Every source in design-canvas/camera-sources.md, * each with its licence status shown first. All default OFF. A licence-gated * source (agreement required / unverified / non-commercial / paid) shows a * blocking warning and needs an explicit confirmation, which c2-core records * with who/when in config/camera_providers and audit_log. Stubs and sources * whose terms forbid the use cannot be switched on at all. */ import { useEffect, useState } from "react"; import { c2api } from "@/lib/c2api"; import type { CameraProviderAdmin, LicenceStatus } from "@/lib/types"; const LICENCE_STYLE: Record = { commercial_ok: { label: "Commercial OK", cls: "bg-green-900 text-green-300 border-green-700" }, attribution_required: { label: "Attribution required", cls: "bg-indigo-950 text-indigo-300 border-indigo-700" }, agreement_required: { label: "Requires agreement — not signed", cls: "bg-red-950 text-red-300 border-red-700" }, non_commercial: { label: "Non-commercial only", cls: "bg-red-950 text-red-300 border-red-700" }, unverified: { label: "Licence unverified", cls: "bg-amber-950 text-amber-300 border-amber-700" }, paid: { label: "Paid licence — no contract", cls: "bg-amber-950 text-amber-300 border-amber-700" }, prohibited: { label: "Terms forbid use", cls: "bg-red-950 text-red-300 border-red-700" }, }; function fmtEpoch(s: number | null | undefined): string { return s ? new Date(s * 1000).toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : "—"; } export function CameraProvidersTab() { const [providers, setProviders] = useState(null); const [error, setError] = useState(null); const [busy, setBusy] = useState(null); const [confirming, setConfirming] = useState(null); const [ackChecked, setAckChecked] = useState(false); const load = () => c2api.listCameraProviders().then((r) => setProviders(r.providers)).catch((e) => setError(String(e))); useEffect(() => { load(); }, []); async function toggle(p: CameraProviderAdmin, enabled: boolean, acknowledge = false) { if (enabled && p.licence.gated && !acknowledge) { setAckChecked(false); setConfirming(p); return; } setBusy(p.id); setError(null); try { const updated = await c2api.setCameraProvider(p.id, enabled, acknowledge); setProviders((cur) => cur?.map((x) => (x.id === p.id ? updated : x)) ?? null); setConfirming(null); } catch (e) { setError(String(e)); } finally { setBusy(null); } } return (

Public camera sources for the Live wall. Every provider is off until enabled here. Licence status comes from the camera-sources catalog (2026-10-04); a red or amber licence means DRB does not hold a confirmed right to show that imagery. Required attributions render on every camera tile.

{error &&
{error}
} {!providers ? (

Loading…

) : (
{providers.map((p) => { const lic = LICENCE_STYLE[p.licence.status]; const lockReason = !p.implemented ? "No working adapter (stub)" : p.licence.status === "prohibited" ? "Source terms forbid this use" : null; return (
{lic.label}

{p.name}

{p.licence.label}

{p.region} · {p.formats}

{p.serving ? "serving" : lockReason ?? p.not_serving_reason ?? "off"}

cameras: {p.health ? p.health.count : "—"} last fetch: {fmtEpoch(p.health?.fetched_at)} health: {p.health ? (p.health.error ? "error" : p.health.fetched_at ? "ok" : "not fetched yet") : "n/a"} {p.key_setting ? `key: ${p.key_missing ? "missing" : "set"}` : "no key needed"}
{p.health?.error &&

{p.health.error}

} {p.licence.attribution &&

Attribution shown with imagery: “{p.licence.attribution}”

} {p.acknowledgement && (

Licence warning acknowledged by {p.acknowledgement.by_email || p.acknowledgement.by_uid} on {new Date(p.acknowledgement.at).toLocaleString()}

)}
Terms & adapter notes

{p.licence.terms}

{p.notes}

{p.licence.terms_url && terms} {p.homepage && source} {p.licence.issue && tracking: {p.licence.issue}}

); })}
)} {confirming && (

Enable {confirming.name}?

{LICENCE_STYLE[confirming.licence.status].label}

{confirming.licence.warning}

)}
); }