'use client'; import { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ArrowLeft, RefreshCw, Play, CheckCircle2, XCircle, Clock, Shield, Monitor, AlertTriangle, Activity, } from 'lucide-react'; function fmtDate(d: string | null) { if (!d) return '—'; return new Date(d).toLocaleString(); } function fmtDuration(ms: number | null) { if (!ms) return '—'; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; return `${(ms / 60000).toFixed(1)}m`; } export default function SentinelOneSyncPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [syncing, setSyncing] = useState(false); const fetchData = useCallback(async () => { try { const res = await fetch('/api/sentinelone/sync'); if (res.ok) setData(await res.json()); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); const interval = setInterval(fetchData, 10000); return () => clearInterval(interval); }, [fetchData]); const triggerSync = async () => { setSyncing(true); try { await fetch('/api/sentinelone/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ triggeredBy: 'manual' }), }); setTimeout(fetchData, 2000); } finally { setSyncing(false); } }; const lastSync = data?.history?.[0]; const counts = data?.counts ?? {}; const inProgress = data?.inProgress ?? false; return (

SentinelOne Sync

Sites, agents, and threats synced to s1_* tables

{/* Stats */}
{[ { label: 'Sites', value: counts.sites, icon: Shield, color: 'text-purple-500' }, { label: 'Agents', value: counts.agents, icon: Monitor, color: 'text-blue-500' }, { label: 'Active', value: counts.active_agents, icon: Activity, color: 'text-green-500' }, { label: 'Infected', value: counts.infected, icon: AlertTriangle, color: 'text-red-500' }, { label: 'Threats', value: counts.threats, icon: XCircle, color: 'text-orange-500' }, ].map(({ label, value, icon: Icon, color }) => ( {label}
{loading ? '—' : (Number(value ?? 0)).toLocaleString()}
))}
{/* Last sync status */} {lastSync && ( Last Sync
{lastSync.status === 'completed' ? : lastSync.status === 'running' ? : }
{lastSync.status}
{fmtDate(lastSync.completed_at || lastSync.started_at)} · {fmtDuration(lastSync.duration_ms)} · {lastSync.total_upserted?.toLocaleString()} records
{lastSync.entity_results && (
{(Array.isArray(lastSync.entity_results) ? lastSync.entity_results : JSON.parse(lastSync.entity_results) ).map((e: any) => (
{e.entity}
{e.success ? {e.recordsUpserted.toLocaleString()} : failed}
))}
)} {lastSync.error_message && (
{lastSync.error_message}
)}
)} {/* History */} Sync History
{(data?.history ?? []).map((h: any) => (
{h.status === 'completed' ? : h.status === 'running' ? : } {fmtDate(h.started_at)}
{h.total_upserted?.toLocaleString() ?? 0} records {fmtDuration(h.duration_ms)} {h.triggered_by}
))} {!loading && (data?.history ?? []).length === 0 && (

No sync history yet — run a sync to get started

)}
); }