Merge pull request 'Outage providers: licence-gated registry, ODIN adapter, /outages API, admin tab (Refs #184)' (#255) from feat/184-outage-providers into main
Build & Deploy / Build & push images (push) Successful in 4m13s
Build & Deploy / Deploy Firestore rules & indexes (push) Successful in 28s
Build & Deploy / Deploy to VM (push) Successful in 1m39s
Build & Deploy / Report a failed deploy (push) Skipped

This commit was merged in pull request #255.
This commit is contained in:
2026-10-05 02:47:15 -04:00
12 changed files with 4715 additions and 4 deletions
+6 -3
View File
@@ -4,6 +4,7 @@ import { useAuth } from "@/components/AuthProvider";
import { c2api } from "@/lib/c2api";
import { ReplayTab } from "@/components/admin/ReplayTab";
import { CameraProvidersTab } from "@/components/admin/CameraProvidersTab";
import { OutageProvidersTab } from "@/components/admin/OutageProvidersTab";
import { useEffect, useState, useRef, useCallback } from "react";
import { useRouter } from "next/navigation";
import type { UserRecord, AuditEntry, UserRole, CallRecord } from "@/lib/types";
@@ -1268,11 +1269,12 @@ function SttEvalTab() {
// Main admin page
// ---------------------------------------------------------------------------
type AdminTab = "features" | "cameras" | "correlation" | "replay" | "users" | "audit" | "calls" | "eval";
type AdminTab = "features" | "cameras" | "outages" | "correlation" | "replay" | "users" | "audit" | "calls" | "eval";
const TAB_LABELS: { key: AdminTab; label: string }[] = [
{ key: "features", label: "AI Features" },
{ key: "cameras", label: "Camera providers" },
{ key: "outages", label: "Outage providers" },
{ key: "correlation", label: "Correlation Debug" },
{ key: "replay", label: "Replay" },
{ key: "calls", label: "Calls" },
@@ -1285,7 +1287,7 @@ export default function AdminPage() {
const { user, isAdmin, loading: authLoading } = useAuth();
const router = useRouter();
const [tab, setTab] = useState<AdminTab>("features");
// Deep link to a tab (/admin#cameras), e.g. from the live wall's empty state.
// Deep link to a tab (/admin#cameras, /admin#outages), e.g. from a map layer's empty state.
useEffect(() => {
const h = window.location.hash.slice(1);
if (TAB_LABELS.some((t) => t.key === h)) setTab(h as AdminTab);
@@ -1304,7 +1306,7 @@ export default function AdminPage() {
if (!isAdmin) return null;
// Users/Audit tabs benefit from full width; everything else is narrow
const wide = tab === "users" || tab === "audit" || tab === "replay" || tab === "cameras";
const wide = tab === "users" || tab === "audit" || tab === "replay" || tab === "cameras" || tab === "outages";
return (
<div className={`space-y-6 ${wide ? "" : "max-w-2xl"}`}>
@@ -1326,6 +1328,7 @@ export default function AdminPage() {
{tab === "features" && <FeaturesTab />}
{tab === "cameras" && <CameraProvidersTab />}
{tab === "outages" && <OutageProvidersTab />}
{tab === "correlation" && <CorrelationDebugTab />}
{tab === "replay" && <ReplayTab />}
{tab === "calls" && <StaleCallsTab />}