- Add SentinelOne API client (lib/services/sentinelone-client.ts) - Paginated fetching for sites, agents, threats - JWT token auth via S1_API_URL / S1_API_TOKEN env vars - Add SentinelOne sync service (lib/services/sentinelone-sync-service.ts) - Full sync: sites, agents, threats into s1_* tables - Sync history tracking with per-entity results - Add DB migration 038: s1_sites, s1_agents, s1_threats, s1_company_mappings, s1_sync_history tables - Add API routes: - POST/GET /api/sentinelone/sync - GET/POST/DELETE /api/sentinelone/company-mappings - GET /api/sentinelone/coverage (fixed Cartesian product bug) - Add UI pages: - /admin/sync/sentinelone — sync admin with history + stats - /sentinelone/coverage — AV coverage report per site - /sentinelone/mappings — map S1 sites to Autotask companies - Wire SentinelOne into admin sync overview card grid - Add SentinelOne Sync to app navigation - Fix docker-compose: remove explicit S1 env var entries that were overwriting env_file values with empty strings
53 lines
1.9 KiB
TypeScript
53 lines
1.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getSentinelOneSyncService } from '@/lib/services/sentinelone-sync-service';
|
|
import { postgresClient } from '@/lib/services/postgres-client';
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = await request.json().catch(() => ({}));
|
|
const triggeredBy = body.triggeredBy || 'manual';
|
|
|
|
const svc = getSentinelOneSyncService();
|
|
if (svc.isSyncInProgress()) {
|
|
return NextResponse.json({ error: 'Sync already in progress' }, { status: 409 });
|
|
}
|
|
|
|
svc.fullSync(triggeredBy).catch(err => console.error('[S1Sync] Background sync error:', err));
|
|
|
|
return NextResponse.json({ success: true, message: 'SentinelOne sync started' });
|
|
} catch (error) {
|
|
const msg = error instanceof Error ? error.message : String(error);
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function GET() {
|
|
try {
|
|
const [historyRes, countsRes] = await Promise.all([
|
|
postgresClient.query(
|
|
`SELECT id, sync_type, status, triggered_by, started_at, completed_at,
|
|
duration_ms, total_upserted, error_message, entity_results
|
|
FROM s1_sync_history ORDER BY started_at DESC LIMIT 10`
|
|
),
|
|
postgresClient.query(
|
|
`SELECT
|
|
(SELECT COUNT(*) FROM s1_sites) AS sites,
|
|
(SELECT COUNT(*) FROM s1_agents) AS agents,
|
|
(SELECT COUNT(*) FROM s1_agents WHERE infected = true) AS infected,
|
|
(SELECT COUNT(*) FROM s1_agents WHERE is_active = true) AS active_agents,
|
|
(SELECT COUNT(*) FROM s1_threats) AS threats`
|
|
),
|
|
]);
|
|
|
|
const svc = getSentinelOneSyncService();
|
|
|
|
return NextResponse.json({
|
|
inProgress: svc.isSyncInProgress(),
|
|
history: historyRes.rows,
|
|
counts: countsRes.rows[0],
|
|
});
|
|
} catch (error) {
|
|
const msg = error instanceof Error ? error.message : String(error);
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
|
}
|
|
}
|