patchcenter/app/templates/qualys_dashboard_history.html

125 lines
6.0 KiB
HTML

{% extends 'base.html' %}
{% block title %}Historique Vulnérabilités{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-cyber-accent">Historique Vulnérabilités</h2>
<a href="/qualys/dashboard" class="btn-sm bg-cyber-border text-cyber-accent">← Dashboard</a>
</div>
<form method="GET" class="card p-3 mb-4 flex gap-3 items-end flex-wrap">
<div>
<label class="text-xs text-gray-500">Granularité</label>
<select name="period" class="text-xs py-1 px-2">
<option value="day" {% if period == 'day' %}selected{% endif %}>Journalier</option>
<option value="week" {% if period == 'week' %}selected{% endif %}>Hebdomadaire (lundi)</option>
<option value="month" {% if period == 'month' %}selected{% endif %}>Mensuel</option>
</select>
</div>
<div>
<label class="text-xs text-gray-500">Période (jours)</label>
<select name="days" class="text-xs py-1 px-2">
<option value="7" {% if days == 7 %}selected{% endif %}>7 jours</option>
<option value="30" {% if days == 30 %}selected{% endif %}>30 jours</option>
<option value="90" {% if days == 90 %}selected{% endif %}>90 jours</option>
<option value="180" {% if days == 180 %}selected{% endif %}>180 jours</option>
<option value="365" {% if days == 365 %}selected{% endif %}>365 jours</option>
</select>
</div>
<div>
<label class="text-xs text-gray-500">Périmètre</label>
<select name="dim_value" class="text-xs py-1 px-2" onchange="
var v = this.value;
var parts = v.split('|');
document.getElementById('dim-input').value = parts[0];
document.getElementById('dimv-input').value = parts[1];
">
<option value="global|all" {% if dimension == 'global' %}selected{% endif %}>Global (tous serveurs)</option>
{% for o in dim_options %}
{% if o.dimension != 'global' %}
<option value="{{ o.dimension }}|{{ o.dimension_value }}"
{% if dimension == o.dimension and dim_value == o.dimension_value %}selected{% endif %}>
{{ o.dimension|upper }} : {{ o.dimension_value }}
</option>
{% endif %}
{% endfor %}
</select>
<input type="hidden" name="dimension" id="dim-input" value="{{ dimension }}">
<input type="hidden" name="dim_value" id="dimv-input" value="{{ dim_value }}">
</div>
<button type="submit" class="btn-primary px-4 py-1 text-sm">Afficher</button>
<span class="text-xs text-gray-500 ml-auto">{{ runs_count }} snapshot(s) total en base</span>
</form>
{% if not series %}
<div class="card p-6 text-center text-gray-400">
Aucune donnée historique pour ce périmètre/période. Lance des snapshots quotidiens pour alimenter.
</div>
{% else %}
<div class="card p-3 mb-4">
<canvas id="chart" style="max-height:400px"></canvas>
</div>
<div class="card p-3">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Données ({{ series|length }} points)</h3>
<table class="w-full text-xs">
<thead class="text-gray-400 border-b border-cyber-border">
<tr>
<th class="text-left py-1">Date</th>
<th class="text-right py-1">Total</th>
<th class="text-right py-1 text-red-400">🔴 Critique</th>
<th class="text-right py-1 text-orange-400">🟠 High</th>
<th class="text-right py-1 text-yellow-400">🟡 Medium</th>
<th class="text-right py-1 text-green-400">🟢 Sain</th>
<th class="text-right py-1 text-gray-500">⚫ Non scanné</th>
<th class="text-right py-1">Total vuln</th>
</tr>
</thead>
<tbody>
{% for p in series|reverse %}
<tr class="border-b border-cyber-border/30">
<td class="py-1 font-mono">{{ p.bucket[:10] }}</td>
<td class="text-right">{{ p.total }}</td>
<td class="text-right text-red-400">{{ p.critical }}</td>
<td class="text-right text-orange-400">{{ p.high }}</td>
<td class="text-right text-yellow-400">{{ p.medium }}</td>
<td class="text-right text-green-400">{{ p.sain }}</td>
<td class="text-right text-gray-500">{{ p.non_scanne }}</td>
<td class="text-right font-bold">{{ p.vuln_total }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<script>
const data = {{ series|tojson }};
const labels = data.map(p => p.bucket.substring(0,10));
new Chart(document.getElementById('chart'), {
type: 'line',
data: {
labels: labels,
datasets: [
{label: '🔴 Critique', data: data.map(p => p.critical), borderColor: '#ef4444', backgroundColor: 'rgba(239,68,68,.1)', tension: .2, fill: true},
{label: '🟠 High', data: data.map(p => p.high), borderColor: '#f97316', backgroundColor: 'rgba(249,115,22,.1)', tension: .2, fill: true},
{label: '🟡 Medium', data: data.map(p => p.medium), borderColor: '#eab308', backgroundColor: 'rgba(234,179,8,.1)', tension: .2, fill: true},
{label: '🟢 Sain', data: data.map(p => p.sain), borderColor: '#22c55e', backgroundColor: 'rgba(34,197,94,.1)', tension: .2, fill: false, hidden: true},
{label: '⚫ Non scanné', data: data.map(p => p.non_scanne), borderColor: '#6b7280', backgroundColor: 'rgba(107,114,128,.1)', tension: .2, fill: false, hidden: true}
]
},
options: {
responsive: true,
plugins: {legend: {labels: {color: '#cbd5e1'}}},
scales: {
x: {ticks: {color: '#94a3b8'}, grid: {color: 'rgba(255,255,255,.05)'}},
y: {ticks: {color: '#94a3b8'}, grid: {color: 'rgba(255,255,255,.05)'}, beginAtZero: true}
}
}
});
</script>
{% endif %}
{% endblock %}