add debug in admin
This commit is contained in:
+148
-29
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useAuth } from "@/components/AuthProvider";
|
||||
import { c2api } from "@/lib/c2api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface FeatureFlags {
|
||||
@@ -61,9 +61,113 @@ function Toggle({
|
||||
);
|
||||
}
|
||||
|
||||
function CorrelationDebugTab() {
|
||||
const [limit, setLimit] = useState(20);
|
||||
const [orphanHours, setOrphanHours] = useState(48);
|
||||
const [data, setData] = useState<unknown>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const preRef = useRef<HTMLPreElement>(null);
|
||||
|
||||
async function handleFetch() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setData(null);
|
||||
setCopied(false);
|
||||
try {
|
||||
const result = await c2api.getCorrelationDebug(limit, orphanHours);
|
||||
setData(result);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCopy() {
|
||||
if (!data) return;
|
||||
await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
|
||||
const json = data ? JSON.stringify(data, null, 2) : null;
|
||||
const meta = data as { incident_count?: number; orphaned_call_count?: number; generated_at?: string } | null;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-gray-500 font-mono">
|
||||
Fetches recent incidents with per-call correlation debug fields, plus orphaned calls.
|
||||
Copy the JSON and paste it to Claude to diagnose correlation problems.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap items-end gap-4">
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Incidents (limit)</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1} max={100}
|
||||
value={limit}
|
||||
onChange={(e) => setLimit(Math.min(100, Math.max(1, Number(e.target.value))))}
|
||||
className="w-24 bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-white text-sm font-mono focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Orphan scan window (hours)</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1} max={168}
|
||||
value={orphanHours}
|
||||
onChange={(e) => setOrphanHours(Math.min(168, Math.max(1, Number(e.target.value))))}
|
||||
className="w-28 bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-white text-sm font-mono focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleFetch}
|
||||
disabled={loading}
|
||||
className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white text-sm font-mono px-4 py-1.5 rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? "Fetching…" : "Fetch"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-950 border border-red-800 rounded-lg p-3">
|
||||
<p className="text-red-400 text-sm font-mono">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{meta && (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs text-gray-500 font-mono">
|
||||
{meta.incident_count} incidents · {meta.orphaned_call_count} orphaned calls · {meta.generated_at}
|
||||
</p>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="text-xs font-mono px-3 py-1.5 rounded-lg border border-gray-700 bg-gray-900 text-gray-300 hover:text-white transition-colors"
|
||||
>
|
||||
{copied ? "Copied!" : "Copy JSON"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{json && (
|
||||
<pre
|
||||
ref={preRef}
|
||||
className="bg-gray-950 border border-gray-800 rounded-xl p-4 text-xs text-gray-300 font-mono overflow-auto max-h-[60vh] whitespace-pre"
|
||||
>
|
||||
{json}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AdminPage() {
|
||||
const { isAdmin } = useAuth();
|
||||
const router = useRouter();
|
||||
const [tab, setTab] = useState<"features" | "correlation">("features");
|
||||
|
||||
const [flags, setFlags] = useState<FeatureFlags | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -98,38 +202,53 @@ export default function AdminPage() {
|
||||
if (!isAdmin) return null;
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl space-y-8">
|
||||
<div className="max-w-2xl space-y-6">
|
||||
<h1 className="text-white text-xl font-bold font-mono">Admin</h1>
|
||||
|
||||
<section className="space-y-3">
|
||||
<h2 className="text-sm font-mono text-gray-400 uppercase tracking-wider">AI Features</h2>
|
||||
<div className="flex gap-1 bg-gray-900 border border-gray-800 rounded-lg p-1 w-fit">
|
||||
{(["features", "correlation"] as const).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
onClick={() => setTab(t)}
|
||||
className={`text-sm font-mono px-4 py-1.5 rounded-md transition-colors ${
|
||||
tab === t ? "bg-gray-800 text-white" : "text-gray-500 hover:text-gray-300"
|
||||
}`}
|
||||
>
|
||||
{t === "features" ? "AI Features" : "Correlation Debug"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-950 border border-red-800 rounded-lg p-3">
|
||||
<p className="text-red-400 text-sm font-mono">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<p className="text-gray-500 text-sm font-mono">Loading…</p>
|
||||
) : (
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl divide-y divide-gray-800">
|
||||
{FLAG_META.map(({ key, label, description }) => (
|
||||
<div key={key} className="flex items-center justify-between gap-4 px-5 py-4">
|
||||
<div className="min-w-0">
|
||||
<p className="text-white text-sm font-semibold">{label}</p>
|
||||
<p className="text-gray-500 text-xs mt-0.5 leading-snug">{description}</p>
|
||||
{tab === "features" && (
|
||||
<section className="space-y-3">
|
||||
{error && (
|
||||
<div className="bg-red-950 border border-red-800 rounded-lg p-3">
|
||||
<p className="text-red-400 text-sm font-mono">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
{loading ? (
|
||||
<p className="text-gray-500 text-sm font-mono">Loading…</p>
|
||||
) : (
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl divide-y divide-gray-800">
|
||||
{FLAG_META.map(({ key, label, description }) => (
|
||||
<div key={key} className="flex items-center justify-between gap-4 px-5 py-4">
|
||||
<div className="min-w-0">
|
||||
<p className="text-white text-sm font-semibold">{label}</p>
|
||||
<p className="text-gray-500 text-xs mt-0.5 leading-snug">{description}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
enabled={flags?.[key] ?? true}
|
||||
onChange={(val) => handleToggle(key, val)}
|
||||
disabled={saving === key}
|
||||
/>
|
||||
</div>
|
||||
<Toggle
|
||||
enabled={flags?.[key] ?? true}
|
||||
onChange={(val) => handleToggle(key, val)}
|
||||
disabled={saving === key}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{tab === "correlation" && <CorrelationDebugTab />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ export const c2api = {
|
||||
request<Record<string, boolean>>("/admin/features"),
|
||||
setFeatureFlags: (flags: Record<string, boolean>) =>
|
||||
request<Record<string, boolean>>("/admin/features", { method: "PUT", body: JSON.stringify(flags) }),
|
||||
getCorrelationDebug: (limit: number, orphanHours: number) =>
|
||||
request<unknown>(`/admin/debug/correlation?limit=${limit}&orphan_hours=${orphanHours}`),
|
||||
|
||||
// Per-system AI flag overrides
|
||||
setSystemAiFlags: (systemId: string, flags: { stt_enabled?: boolean | null; correlation_enabled?: boolean | null }) =>
|
||||
|
||||
Reference in New Issue
Block a user