updated dash
This commit is contained in:
@@ -4,10 +4,11 @@
|
||||
var pollInterval = (window._pollInterval || 15) * 1000;
|
||||
var cliAvailable = !!window._cliAvailable;
|
||||
|
||||
var elDecisions = document.getElementById('stat-decisions');
|
||||
var elAlerts = document.getElementById('stat-alerts');
|
||||
var elBouncers = document.getElementById('stat-bouncers');
|
||||
var elMachines = document.getElementById('stat-machines');
|
||||
var elDecisions = document.getElementById('stat-decisions');
|
||||
var elAlerts24h = document.getElementById('stat-alerts-24h');
|
||||
var elAlerts7d = document.getElementById('stat-alerts-7d');
|
||||
var elBouncers = document.getElementById('stat-bouncers');
|
||||
var elMachines = document.getElementById('stat-machines');
|
||||
|
||||
function animateTo(el, newVal) {
|
||||
if (!el) return;
|
||||
@@ -32,7 +33,8 @@
|
||||
})
|
||||
.then(function (d) {
|
||||
animateTo(elDecisions, d.decisions);
|
||||
animateTo(elAlerts, d.alerts);
|
||||
animateTo(elAlerts24h, d.alerts_24h);
|
||||
animateTo(elAlerts7d, d.alerts_7d);
|
||||
if (cliAvailable) {
|
||||
animateTo(elBouncers, d.bouncers);
|
||||
animateTo(elMachines, d.machines);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-body" style="padding:12px 18px">
|
||||
<form method="GET" action="/alerts" class="filter-bar">
|
||||
<form method="GET" action="/alerts" class="filter-bar" id="filter-form">
|
||||
<input class="filter-input" type="text" name="scenario" placeholder="Scenario filter..." value="{{.Filter.Scenario}}">
|
||||
<input class="filter-input" type="text" name="ip" placeholder="Source IP..." value="{{.Filter.IP}}">
|
||||
<select class="filter-select" name="since">
|
||||
@@ -18,8 +18,9 @@
|
||||
<option value="24h" {{if eq .Filter.Since "24h"}}selected{{end}}>Last 24h</option>
|
||||
<option value="7d" {{if eq .Filter.Since "7d"}}selected{{end}}>Last 7d</option>
|
||||
</select>
|
||||
{{if .ShowUpdates}}<input type="hidden" name="show_updates" value="1">{{end}}
|
||||
<button type="submit" class="btn-secondary">Filter</button>
|
||||
{{if or .Filter.Scenario .Filter.IP .Filter.Since}}<a href="/alerts" class="btn-ghost">Clear</a>{{end}}
|
||||
{{if or .Filter.Scenario .Filter.IP .Filter.Since}}<a href="/alerts{{if .ShowUpdates}}?show_updates=1{{end}}" class="btn-ghost">Clear</a>{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,8 +29,9 @@
|
||||
<form method="POST" action="/alerts/delete" id="bulk-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Alerts ({{len .Alerts}} shown)</span>
|
||||
<span class="panel-title">Alerts <span id="visible-count"></span></span>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button type="button" class="btn-ghost-sm" id="toggle-updates" onclick="toggleUpdates()">{{if .ShowUpdates}}Hide updates{{else}}Show updates{{end}}</button>
|
||||
<button type="button" class="btn-ghost-sm" onclick="toggleAll()">Select all</button>
|
||||
<button type="submit" class="btn-danger-sm" onclick="return confirmBulkDelete()">Delete selected</button>
|
||||
</div>
|
||||
@@ -51,12 +53,12 @@
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="alerts-tbody">
|
||||
{{range .Alerts}}
|
||||
<tr>
|
||||
<tr data-scenario="{{.Scenario}}">
|
||||
<td><input type="checkbox" name="id" value="{{.ID}}" class="row-chk"></td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">{{.ID}}</td>
|
||||
<td style="font-size:12px" title="{{.Scenario}}">{{truncate .Scenario 36}}</td>
|
||||
<td style="font-size:12px" title="{{.Scenario}}">{{truncate .Scenario 40}}</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">{{.Source.Value}}</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{.Source.CN}}</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace">{{.EventsCount}}</td>
|
||||
@@ -74,6 +76,19 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-top:1px solid var(--border)">
|
||||
<div style="font-size:12px;color:var(--muted)">Page {{.Page}} · {{len .Alerts}} per page</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
{{if gt .Page 1}}
|
||||
<a href="{{alertsPageURL .Filter .Page false .ShowUpdates}}" class="btn-ghost-sm">Previous</a>
|
||||
{{end}}
|
||||
{{if .HasNext}}
|
||||
<a href="{{alertsPageURL .Filter .Page true .ShowUpdates}}" class="btn-ghost-sm">Next</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<div class="empty-text">No alerts found</div>
|
||||
@@ -87,6 +102,44 @@
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
var showUpdates = {{if .ShowUpdates}}true{{else}}false{{end}};
|
||||
|
||||
function applyFilter() {
|
||||
var rows = document.querySelectorAll('#alerts-tbody tr');
|
||||
var visible = 0;
|
||||
rows.forEach(function(row) {
|
||||
var scenario = row.getAttribute('data-scenario') || '';
|
||||
var isUpdate = scenario.startsWith('update :') || scenario.startsWith('update:');
|
||||
if (!showUpdates && isUpdate) {
|
||||
row.style.display = 'none';
|
||||
} else {
|
||||
row.style.display = '';
|
||||
visible++;
|
||||
}
|
||||
});
|
||||
var el = document.getElementById('visible-count');
|
||||
if (el) el.textContent = '(' + visible + ' shown)';
|
||||
}
|
||||
|
||||
function toggleUpdates() {
|
||||
showUpdates = !showUpdates;
|
||||
var btn = document.getElementById('toggle-updates');
|
||||
if (btn) btn.textContent = showUpdates ? 'Hide updates' : 'Show updates';
|
||||
applyFilter();
|
||||
// persist across pages: update filter form hidden input
|
||||
var form = document.getElementById('filter-form');
|
||||
var existing = form.querySelector('input[name="show_updates"]');
|
||||
if (showUpdates) {
|
||||
if (!existing) {
|
||||
var inp = document.createElement('input');
|
||||
inp.type = 'hidden'; inp.name = 'show_updates'; inp.value = '1';
|
||||
form.appendChild(inp);
|
||||
}
|
||||
} else {
|
||||
if (existing) existing.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function selectAll(cb) {
|
||||
document.querySelectorAll('.row-chk').forEach(function(c) { c.checked = cb.checked; });
|
||||
}
|
||||
@@ -100,5 +153,7 @@ function confirmBulkDelete() {
|
||||
if (n === 0) { alert('Select at least one alert.'); return false; }
|
||||
return confirm('Delete ' + n + ' alert(s)?');
|
||||
}
|
||||
|
||||
applyFilter();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -2,16 +2,21 @@
|
||||
{{define "content"}}
|
||||
<div style="max-width:1400px">
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:24px">
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px">
|
||||
<div class="stat-card stat-card--threat">
|
||||
<div class="stat-label">Active Bans</div>
|
||||
<div class="stat-value" id="stat-decisions">—</div>
|
||||
<div class="stat-sub">decisions in effect</div>
|
||||
</div>
|
||||
<div class="stat-card stat-card--warn">
|
||||
<div class="stat-label">Recent Alerts</div>
|
||||
<div class="stat-value" id="stat-alerts">—</div>
|
||||
<div class="stat-sub">up to 500 counted</div>
|
||||
<div class="stat-label">Alerts (24h)</div>
|
||||
<div class="stat-value" id="stat-alerts-24h">—</div>
|
||||
<div class="stat-sub">last 24 hours</div>
|
||||
</div>
|
||||
<div class="stat-card stat-card--warn">
|
||||
<div class="stat-label">Alerts (7d)</div>
|
||||
<div class="stat-value" id="stat-alerts-7d">—</div>
|
||||
<div class="stat-sub">last 7 days</div>
|
||||
</div>
|
||||
<div class="stat-card stat-card--accent">
|
||||
<div class="stat-label">Bouncers</div>
|
||||
@@ -100,9 +105,9 @@
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/dashboard.js"></script>
|
||||
<script>
|
||||
window._pollInterval = {{.PollInterval}};
|
||||
window._cliAvailable = {{.CLIAvailable}};
|
||||
</script>
|
||||
<script src="/static/js/dashboard.js"></script>
|
||||
{{end}}
|
||||
|
||||
@@ -52,11 +52,20 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if .CLIAvailable}}
|
||||
{{if .RawOutput}}
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-header"><span class="panel-title">Raw cscli metrics output</span></div>
|
||||
<div class="panel-body" style="padding:12px 18px">
|
||||
<pre style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--muted);white-space:pre-wrap;word-break:break-all;max-height:600px;overflow-y:auto">{{.RawOutput}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state" style="padding:48px">
|
||||
<div class="empty-text">No metrics available</div>
|
||||
<div class="empty-sub">CrowdSec may not have processed any data yet</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user