stale calls fix

This commit is contained in:
Logan
2026-06-22 00:06:10 -04:00
parent 1f17b6c0d2
commit 3defdf18dc
2 changed files with 97 additions and 6 deletions
+4 -1
View File
@@ -78,8 +78,11 @@ async def close_stale_calls(
started_raw = call.get("started_at")
if not started_raw:
continue
if isinstance(started_raw, datetime):
started = started_raw if started_raw.tzinfo else started_raw.replace(tzinfo=timezone.utc)
else:
try:
started = datetime.fromisoformat(started_raw.replace("Z", "+00:00"))
started = datetime.fromisoformat(str(started_raw).replace("Z", "+00:00"))
except Exception:
continue
if started < cutoff:
+90 -2
View File
@@ -961,15 +961,102 @@ function AuditLogTab() {
);
}
// ---------------------------------------------------------------------------
// Stale Calls tab
// ---------------------------------------------------------------------------
function StaleCallsTab() {
const [minutes, setMinutes] = useState(30);
const [result, setResult] = useState<{ dry_run: boolean; count: number; call_ids: string[] } | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function run(dryRun: boolean) {
setLoading(true);
setError(null);
setResult(null);
try {
const res = await c2api.closeStallCalls(minutes, dryRun);
setResult(res);
} catch (e) {
setError(String(e));
} finally {
setLoading(false);
}
}
return (
<div className="space-y-5">
<p className="text-xs text-gray-500 font-mono">
Finds calls stuck in <span className="text-gray-300">active</span> status because a node rebooted before sending an end-call event.
Preview first, then close.
</p>
<div className="flex flex-wrap items-end gap-4">
<div>
<label className="text-xs text-gray-400 block mb-1">Older than (minutes)</label>
<input
type="number"
min={1} max={1440}
value={minutes}
onChange={(e) => setMinutes(Math.min(1440, 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={() => run(true)}
disabled={loading}
className="bg-gray-800 hover:bg-gray-700 disabled:opacity-50 border border-gray-700 text-white text-sm font-mono px-4 py-1.5 rounded-lg transition-colors"
>
{loading ? "Working…" : "Preview"}
</button>
<button
onClick={() => run(false)}
disabled={loading || result === null || result.count === 0}
className="bg-red-700 hover:bg-red-600 disabled:opacity-50 text-white text-sm font-mono px-4 py-1.5 rounded-lg transition-colors"
>
{result && !result.dry_run ? "Closed" : result?.count ? `Close ${result.count} calls` : "Close calls"}
</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>
)}
{result && (
<div className="bg-gray-900 border border-gray-800 rounded-xl p-4 space-y-2">
<p className="text-sm font-mono text-white">
{result.dry_run ? "Preview: " : "Closed: "}
<span className={result.count > 0 ? "text-amber-400" : "text-green-400"}>
{result.count} stale call{result.count !== 1 ? "s" : ""}
</span>
{result.count === 0 && <span className="text-gray-500"> nothing to clear</span>}
</p>
{result.call_ids.length > 0 && (
<div className="max-h-40 overflow-y-auto space-y-0.5">
{result.call_ids.map((id) => (
<p key={id} className="text-xs font-mono text-gray-400">{id}</p>
))}
</div>
)}
</div>
)}
</div>
);
}
// ---------------------------------------------------------------------------
// Main admin page
// ---------------------------------------------------------------------------
type AdminTab = "features" | "correlation" | "users" | "audit";
type AdminTab = "features" | "correlation" | "users" | "audit" | "calls";
const TAB_LABELS: { key: AdminTab; label: string }[] = [
{ key: "features", label: "AI Features" },
{ key: "correlation", label: "Correlation Debug" },
{ key: "calls", label: "Calls" },
{ key: "users", label: "Users" },
{ key: "audit", label: "Audit Log" },
];
@@ -985,7 +1072,7 @@ export default function AdminPage() {
if (!isAdmin) return null;
// Users/Audit tabs benefit from full width; AI Features / Correlation are narrow
// Users/Audit tabs benefit from full width; everything else is narrow
const wide = tab === "users" || tab === "audit";
return (
@@ -1008,6 +1095,7 @@ export default function AdminPage() {
{tab === "features" && <FeaturesTab />}
{tab === "correlation" && <CorrelationDebugTab />}
{tab === "calls" && <StaleCallsTab />}
{tab === "users" && <UsersTab currentUid={user?.uid ?? ""} />}
{tab === "audit" && <AuditLogTab />}
</div>