feat: Mimecast email integration — message logs, threat events, 120d retention, admin UI
This commit is contained in:
parent
7792e91587
commit
25bb70cfa6
11 changed files with 112285 additions and 1 deletions
|
|
@ -23,6 +23,7 @@ const INTEGRATIONS: IntegrationCard[] = [
|
|||
{ id: 'auvik', category: 'NMS', product: 'Auvik', description: 'Network devices, topology, tenant mappings', href: '/admin/sync/auvik', logo: '/logos/auvik.ico', color: 'purple' },
|
||||
{ id: 'addigy', category: 'Apple RMM', product: 'Addigy', description: 'macOS/iOS device management, policies, compliance', href: '/admin/sync/addigy', logo: '/logos/addigy.ico', color: 'gray' },
|
||||
{ id: 'sentinelone', category: 'EDR/AV', product: 'SentinelOne', description: 'Endpoint agents, threat detections, site coverage, AV health', href: '/admin/sync/sentinelone', logo: '/logos/sentinelone.ico', color: 'purple' },
|
||||
{ id: 'mimecast', category: 'Email Security', product: 'Mimecast', description: 'Message tracking logs, threat events, SIEM data, 120-day retention', href: '/admin/sync/mimecast', logo: '/logos/mimecast.ico', color: 'blue' },
|
||||
];
|
||||
|
||||
const COLOR_MAP: Record<string, { bg: string; border: string }> = {
|
||||
|
|
@ -54,13 +55,16 @@ export default function SyncOverviewPage() {
|
|||
const [itglueSyncData, setItglueSyncData] = useState<any>(null);
|
||||
const [s1SyncData, setS1SyncData] = useState<any>(null);
|
||||
|
||||
const [mimecastData, setMimecastData] = useState<any>(null);
|
||||
|
||||
const fetchAll = async () => {
|
||||
try {
|
||||
const [intRes, atRes, itgRes, s1Res] = await Promise.all([
|
||||
const [intRes, atRes, itgRes, s1Res, mcRes] = await Promise.all([
|
||||
fetch('/api/integrations/status'),
|
||||
fetch('/api/sync/last-sync'),
|
||||
fetch('/api/itglue/sync'),
|
||||
fetch('/api/sentinelone/sync'),
|
||||
fetch('/api/mimecast/status'),
|
||||
]);
|
||||
if (intRes.ok) setStatus(await intRes.json());
|
||||
if (atRes.ok) {
|
||||
|
|
@ -69,6 +73,7 @@ export default function SyncOverviewPage() {
|
|||
}
|
||||
if (itgRes.ok) setItglueSyncData(await itgRes.json());
|
||||
if (s1Res.ok) setS1SyncData(await s1Res.json());
|
||||
if (mcRes.ok) setMimecastData(await mcRes.json());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
|
@ -147,6 +152,16 @@ export default function SyncOverviewPage() {
|
|||
}
|
||||
if (id === 'auvik') return { lastSync: status.auvik?.lastSync, configured: status.auvik?.configured };
|
||||
if (id === 'addigy') return { lastSync: status.addigy?.lastSync, configured: status.addigy?.configured };
|
||||
if (id === 'mimecast') {
|
||||
if (!mimecastData) return null;
|
||||
const s = mimecastData.stats ?? {};
|
||||
return {
|
||||
lastSync: s.lastSync ?? null,
|
||||
connected: mimecastData.connected ?? false,
|
||||
messages: s.messages ?? 0,
|
||||
threats: s.threats ?? 0,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
@ -176,6 +191,11 @@ export default function SyncOverviewPage() {
|
|||
if (summary.infected > 0) return <AlertTriangle className="w-4 h-4 text-red-500" />;
|
||||
return <CheckCircle2 className="w-4 h-4 text-green-500" />;
|
||||
}
|
||||
if (id === 'mimecast') {
|
||||
if (!summary.connected) return <Clock className="w-4 h-4 text-muted-foreground" />;
|
||||
if ((summary as any).threats > 0) return <AlertTriangle className="w-4 h-4 text-yellow-500" />;
|
||||
return <CheckCircle2 className="w-4 h-4 text-green-500" />;
|
||||
}
|
||||
return <CheckCircle2 className="w-4 h-4 text-green-500" />;
|
||||
};
|
||||
|
||||
|
|
@ -322,6 +342,24 @@ export default function SyncOverviewPage() {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
{intg.id === 'mimecast' && summary && (
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
<span>Last sync</span>
|
||||
<span className="font-medium text-foreground">{fmtDate((summary as any).lastSync)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Messages</span>
|
||||
<span className="font-medium text-foreground">{((summary as any).messages ?? 0).toLocaleString()}</span>
|
||||
</div>
|
||||
{(summary as any).threats > 0 && (
|
||||
<div className="flex justify-between text-yellow-700">
|
||||
<span>Threat events</span>
|
||||
<span className="font-medium">{(summary as any).threats}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{(intg.id === 'auvik' || intg.id === 'addigy') && (
|
||||
<div className="flex justify-between">
|
||||
<span>Status</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue