Files
BRISTO/resources/views/cortex/index.blade.php
jeremy bayse 89a369964d Premier commit
2026-02-09 11:27:21 +01:00

324 lines
16 KiB
PHP

@extends('layouts.app')
@section('title', 'Cortex XDR')
@section('content')
<div class="mb-4">
<!-- Configuration Warning (Server-side check) -->
<!-- We can't check config in view easily if controller doesn't pass it,
but we'll handle errors in JS -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h5 class="text-muted">Vue d'ensemble de la sécurité</h5>
</div>
<div>
<span class="badge bg-dark border border-secondary p-2" id="api-status-badge">
<span class="spinner-border spinner-border-sm text-secondary me-1" role="status" aria-hidden="true" id="status-spinner"></span>
<span id="api-status-text">Connexion...</span>
</span>
</div>
</div>
<!-- Loader Overlay -->
<div id="loading-overlay" class="text-center py-5">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted">Chargement des données Cortex XDR en cours...</p>
<p class="small text-muted">Cela peut prendre quelques secondes (récupération de ~1000 postes).</p>
</div>
<!-- Main Content (Hidden initially) -->
<div id="dashboard-content" class="d-none">
<!-- KPI Cards -->
<div class="row g-4 mb-4">
<!-- Incidents Total -->
<div class="col-md-3">
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted mb-0">Total Incidents</h6>
<div class="icon-shape bg-primary bg-opacity-10 text-primary rounded-circle p-2">
<i class="bi bi-bug fs-4"></i>
</div>
</div>
<h2 class="display-6 fw-bold mb-0 text-white" id="incidents-total">-</h2>
</div>
</div>
</div>
<!-- Endpoints Total -->
<div class="col-md-3">
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted mb-0">Total Postes</h6>
<div class="icon-shape bg-info bg-opacity-10 text-info rounded-circle p-2">
<i class="bi bi-laptop fs-4"></i>
</div>
</div>
<h2 class="display-6 fw-bold mb-0 text-white" id="endpoints-total">-</h2>
<div class="mt-2 text-muted small">
<i class="bi bi-wifi text-success"></i> <span id="endpoints-connected-sm">-</span> Connectés
</div>
</div>
</div>
</div>
<!-- Critical Incidents -->
<div class="col-md-3">
<div class="card shadow-sm border-0 h-100 position-relative overflow-hidden">
<div class="position-absolute top-0 start-0 w-1 pt-1 h-100 bg-danger"></div>
<div class="card-body ps-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-danger mb-0">Critiques</h6>
<i class="bi bi-lightning-charge-fill text-danger fs-4"></i>
</div>
<h2 class="display-6 fw-bold mb-0 text-white" id="incidents-critical">-</h2>
<small class="text-muted">Incidents nécessitant attention immédiate</small>
</div>
</div>
</div>
<!-- High Incidents -->
<div class="col-md-3">
<div class="card shadow-sm border-0 h-100 position-relative overflow-hidden">
<div class="position-absolute top-0 start-0 w-1 pt-1 h-100 bg-warning"></div>
<div class="card-body ps-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-warning mb-0">Élevés</h6>
<i class="bi bi-exclamation-circle-fill text-warning fs-4"></i>
</div>
<h2 class="display-6 fw-bold mb-0 text-white" id="incidents-high">-</h2>
<small class="text-muted">Incidents à traiter rapidement</small>
</div>
</div>
</div>
</div>
<!-- Detailed Breakdown Row -->
<div class="row g-4">
<!-- Recent Incidents Table -->
<div class="col-md-8">
<div class="card shadow-sm border-0 h-100">
<div class="card-header border-bottom-0 bg-transparent py-3">
<h5 class="card-title fw-bold text-white mb-0">
<i class="bi bi-clock-history me-2 text-primary"></i>Derniers Incidents
</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th class="ps-4">ID</th>
<th>Observed Hosts</th>
<th>Sévérité</th>
<th>Statut</th>
<th>Date</th>
</tr>
</thead>
<tbody id="incidents-table-body">
<!-- Rows injected by JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Endpoints Status -->
<div class="col-md-4">
<div class="card shadow-sm border-0 h-100">
<div class="card-header border-bottom-0 bg-transparent py-3">
<h5 class="card-title fw-bold text-white mb-0">
<i class="bi bi-hdd-network me-2 text-info"></i>État du Parc
</h5>
</div>
<div class="card-body">
<div class="mb-4">
<div class="d-flex justify-content-between mb-1">
<span class="text-white">Connectés</span>
<span class="text-success fw-bold" id="endpoints-connected">-</span>
</div>
<div class="progress" style="height: 6px;">
<div id="progress-connected" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
</div>
</div>
<div class="mb-4">
<div class="d-flex justify-content-between mb-1">
<span class="text-white">Déconnectés</span>
<span class="text-secondary fw-bold" id="endpoints-disconnected">-</span>
</div>
<div class="progress" style="height: 6px;">
<div id="progress-disconnected" class="progress-bar bg-secondary" role="progressbar" style="width: 0%"></div>
</div>
</div>
<!-- Endpoint Types Breakdown -->
<div class="mb-4">
<h6 class="text-white small fw-bold mb-2">Répartition par Type</h6>
<div class="d-flex flex-wrap gap-2" id="endpoint-types-container">
<span class="text-muted small">Chargement...</span>
</div>
</div>
<div class="alert alert-dark border-secondary mt-3 mb-0">
<div class="d-flex">
<i class="bi bi-info-circle me-2 text-info"></i>
<div class="small text-muted">
Le parc est majoritairement <strong class="text-white" id="network-status-text">...</strong>.
<span id="disconnected-count-text">0</span> machines n'ont pas communiqué récemment.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
fetchData();
});
function fetchData() {
fetch("{{ route('cortex.data') }}")
.then(response => {
if (!response.ok) {
throw new Error('Erreur réseau ou configuration');
}
return response.json();
})
.then(data => {
console.log("Cortex Data Received:", data); // Debug
// Hide loader, show content
document.getElementById('loading-overlay').classList.add('d-none');
document.getElementById('dashboard-content').classList.remove('d-none');
// Update Status Badge
const statusText = document.getElementById('api-status-text');
const spinner = document.getElementById('status-spinner');
if (data.mock) {
statusText.innerText = 'Demo Mode';
} else {
statusText.innerText = 'Connecté';
const checkIcon = document.querySelector('.bi-shield-check');
if(checkIcon) checkIcon.classList.remove('d-none');
}
spinner.classList.add('d-none');
// Update Counters safely
document.getElementById('incidents-total').innerText = data.incidents_total || 0;
document.getElementById('endpoints-total').innerText = data.endpoints_total || 0;
document.getElementById('endpoints-connected-sm').innerText = data.endpoints_connected || 0;
document.getElementById('incidents-critical').innerText = data.incidents_critical || 0;
document.getElementById('incidents-high').innerText = data.incidents_high || 0;
// Update Breakdown
document.getElementById('endpoints-connected').innerText = data.endpoints_connected || 0;
document.getElementById('endpoints-disconnected').innerText = data.endpoints_disconnected || 0;
// Progress Bars
const total = (data.endpoints_total && data.endpoints_total > 0) ? data.endpoints_total : 1;
const connPct = ((data.endpoints_connected || 0) / total) * 100;
const discPct = ((data.endpoints_disconnected || 0) / total) * 100;
document.getElementById('progress-connected').style.width = connPct + '%';
document.getElementById('progress-disconnected').style.width = discPct + '%';
// Endpoint Types
const typesContainer = document.getElementById('endpoint-types-container');
typesContainer.innerHTML = '';
if (data.endpoints_types && typeof data.endpoints_types === 'object') {
Object.keys(data.endpoints_types).forEach(type => {
const count = data.endpoints_types[type];
let icon = 'bi-pc-display';
if (type.includes('Server')) icon = 'bi-hdd-rack';
else if (type.includes('Mobile') || type.includes('Phone')) icon = 'bi-phone';
else if (type.includes('Lab') || type.includes('Virtual')) icon = 'bi-box';
typesContainer.innerHTML += `
<div class="d-flex align-items-center bg-dark border border-secondary rounded px-2 py-1">
<i class="bi ${icon} me-2 text-muted"></i>
<span class="text-white small me-2">${type}</span>
<span class="badge bg-secondary rounded-pill">${count}</span>
</div>
`;
});
} else {
typesContainer.innerHTML = '<span class="text-muted small">Aucune donnée de type</span>';
}
// Info Text
// Info Text
document.getElementById('network-status-text').innerText = connPct > 50 ? 'connecté' : 'déconnecté';
document.getElementById('disconnected-count-text').innerText = data.endpoints_disconnected || 0;
// Render Table
const tbody = document.getElementById('incidents-table-body');
let tableContent = '';
if (data.recent_incidents && Array.isArray(data.recent_incidents) && data.recent_incidents.length > 0) {
data.recent_incidents.forEach(incident => {
const sev = (incident.severity || 'unknown').toString().toLowerCase();
let badgeClass = 'light';
if (sev === 'critical') badgeClass = 'danger';
else if (sev === 'high') badgeClass = 'warning';
else if (sev === 'medium') badgeClass = 'info';
else if (sev === 'low') badgeClass = 'secondary';
const dateStr = incident.creation_time ? new Date(incident.creation_time).toLocaleString() : '-';
const link = incident.xdr_url || '#';
const host = (incident.hosts && incident.hosts.length > 0) ? incident.hosts[0] : 'Unknown Host';
const status = incident.status || 'Active';
tableContent += `
<tr>
<td class="ps-4 text-white">#${incident.incident_id || '-'}</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-dark rounded-circle me-2 d-flex justify-content-center align-items-center" style="width:30px;height:30px">
<i class="bi bi-pc-display text-muted small"></i>
</div>
<a href="${link}" target="_blank">${host}</a>
</div>
</td>
<td>
<span class="badge bg-${badgeClass}">${sev.charAt(0).toUpperCase() + sev.slice(1)}</span>
</td>
<td>
<span class="badge bg-dark border border-secondary">${status}</span>
</td>
<td class="text-muted">${dateStr}</td>
</tr>
`;
});
} else {
tableContent = `
<tr>
<td colspan="5" class="text-center py-5 text-muted">
<i class="bi bi-check-circle fs-1 d-block mb-3 text-success opacity-50"></i>
Aucun incident récent à afficher.
</td>
</tr>
`;
}
tbody.innerHTML = tableContent;
}).catch(error => {
console.error(error);
document.getElementById('loading-overlay').innerHTML = `
<div class="alert alert-danger">Erreur de chargement: ${error.message}</div>
<button class="btn btn-outline-light btn-sm mt-2" onclick="location.reload()">Réessayer</button>
`;
});
}
</script>
@endsection