geoip and config editor online
This commit is contained in:
@@ -73,6 +73,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/modal.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
document.body.classList.add('ready');
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<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>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm-fn="confirmBulkDelete">Delete selected</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">{{.ID}}</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-size:12px;color:var(--muted)">{{if .Source.CN}}{{countryFlag .Source.CN}} {{.Source.CN}}{{end}}</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace">{{.EventsCount}}</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace">{{len .Decisions}}</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{truncate .StartAt 16}}</td>
|
||||
@@ -68,7 +68,7 @@
|
||||
<form method="POST" action="/alerts/delete" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
<button type="submit" class="btn-danger-sm" onclick="return confirm('Delete alert #{{.ID}}?')">Delete</button>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm="Delete alert #{{.ID}}?">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -150,8 +150,8 @@ function toggleAll() {
|
||||
}
|
||||
function confirmBulkDelete() {
|
||||
var n = document.querySelectorAll('.row-chk:checked').length;
|
||||
if (n === 0) { alert('Select at least one alert.'); return false; }
|
||||
return confirm('Delete ' + n + ' alert(s)?');
|
||||
if (n === 0) { appModal.info('Select at least one alert.'); return null; }
|
||||
return 'Delete ' + n + ' alert(s)? This cannot be undone.';
|
||||
}
|
||||
|
||||
applyFilter();
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
{{template "base" .}}
|
||||
{{define "content"}}
|
||||
<div style="max-width:1400px">
|
||||
|
||||
<div style="margin-bottom:16px">
|
||||
<div class="page-title">Allowlist</div>
|
||||
<div class="page-sub">IPs and ranges exempt from CrowdSec decisions</div>
|
||||
</div>
|
||||
|
||||
{{if .FetchErr}}
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-body">
|
||||
<div class="empty-text">Allowlist unavailable</div>
|
||||
<div class="empty-sub">{{.FetchErr}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Add Entry</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="/allowlist/add">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px">
|
||||
<div>
|
||||
<label class="field-label">Allowlist Name</label>
|
||||
<input class="field-input" type="text" name="list" placeholder="default" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label">Value (IP or CIDR)</label>
|
||||
<input class="field-input" type="text" name="value" placeholder="1.2.3.4 or 1.2.3.0/24" required>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end">
|
||||
<button type="submit" class="btn-primary">Add to Allowlist</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .Lists}}
|
||||
{{range .Lists}}
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">{{.Name}}</span>
|
||||
{{if .Description}}<span style="font-size:12px;color:var(--muted)">{{.Description}}</span>{{end}}
|
||||
</div>
|
||||
{{if .Items}}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Value</th>
|
||||
<th>Comment</th>
|
||||
<th>Expiry</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{$listName := .Name}}
|
||||
{{range .Items}}
|
||||
<tr>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">{{.Value}}</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{.Comment}}</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{if .Expiry}}{{.Expiry}}{{else}}permanent{{end}}</td>
|
||||
<td>
|
||||
<form method="POST" action="/allowlist/remove" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="list" value="{{$listName}}">
|
||||
<input type="hidden" name="value" value="{{.Value}}">
|
||||
<button type="submit" class="btn-danger-sm"
|
||||
data-confirm="Remove {{.Value}} from allowlist?">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<div class="empty-text">No entries in {{.Name}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="panel">
|
||||
<div class="empty-state">
|
||||
<div class="empty-text">No allowlists found</div>
|
||||
<div class="empty-sub">Create an allowlist with cscli allowlists create, then add entries above</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}{{end}}
|
||||
@@ -72,7 +72,7 @@
|
||||
<form method="POST" action="/bouncers/delete" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="name" value="{{.Name}}">
|
||||
<button type="submit" class="btn-danger-sm" onclick="return confirm('Delete bouncer "{{.Name}}"? This cannot be undone.')">Delete</button>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm="Delete bouncer {{.Name}}? This cannot be undone.">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
{{end}}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
{{template "base" .}}
|
||||
{{define "content"}}
|
||||
<div style="max-width:1400px">
|
||||
|
||||
<div style="margin-bottom:16px">
|
||||
<div class="page-title">Config Editor</div>
|
||||
<div class="page-sub">Edit CrowdSec YAML files — config is tested before applying, reverted on failure</div>
|
||||
</div>
|
||||
|
||||
{{if .FetchErr}}
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<div class="empty-text">Config directory unavailable</div>
|
||||
<div class="empty-sub">{{.FetchErr}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<div style="display:grid;grid-template-columns:220px 1fr;gap:16px">
|
||||
|
||||
<div class="panel" style="align-self:start">
|
||||
<div class="panel-header"><span class="panel-title">Files</span></div>
|
||||
<div style="padding:8px 0">
|
||||
{{if .Files}}
|
||||
{{range .Files}}
|
||||
<a href="/config-editor?file={{.}}"
|
||||
style="display:block;padding:6px 16px;font-family:'JetBrains Mono',monospace;font-size:12px;
|
||||
color:{{if eq . $.File}}var(--accent){{else}}var(--text){{end}};
|
||||
background:{{if eq . $.File}}rgba(0,212,255,0.08){{else}}transparent{{end}};
|
||||
text-decoration:none;word-break:break-all"
|
||||
title="{{.}}">{{.}}</a>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div style="padding:12px 16px;font-size:12px;color:var(--muted)">No YAML files found</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{if .File}}
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title" style="font-family:'JetBrains Mono',monospace;font-size:13px">{{.File}}</span>
|
||||
<span style="font-size:12px;color:var(--muted)">Write saves and runs crowdsec -t; reverts on failure</span>
|
||||
</div>
|
||||
{{if .TestOut}}
|
||||
<div style="padding:12px 16px;background:rgba(255,59,59,0.08);border-bottom:1px solid var(--border)">
|
||||
<div style="font-size:12px;color:var(--threat);margin-bottom:4px">Config test failed — file was reverted</div>
|
||||
<pre style="font-size:11px;color:var(--muted);margin:0;white-space:pre-wrap">{{.TestOut}}</pre>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="/config-editor/save">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<input type="hidden" name="file" value="{{.File}}">
|
||||
<textarea name="content" id="editor"
|
||||
style="width:100%;min-height:500px;font-family:'JetBrains Mono',monospace;font-size:13px;
|
||||
background:var(--surface);color:var(--text);border:1px solid var(--border);
|
||||
border-radius:4px;padding:12px;resize:vertical;box-sizing:border-box;line-height:1.5"
|
||||
spellcheck="false">{{.Content}}</textarea>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:12px">
|
||||
<a href="/config-editor?file={{.File}}" class="btn-ghost">Reset</a>
|
||||
<button type="submit" class="btn-primary"
|
||||
data-confirm="Save {{.File}}?\n\nThe config will be tested with crowdsec -t before applying. If the test fails, the file will be reverted automatically."
|
||||
data-confirm-label="Save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="panel">
|
||||
<div class="empty-state">
|
||||
<div class="empty-text">Select a file</div>
|
||||
<div class="empty-sub">Choose a YAML file from the list to view and edit it</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
// Tab key inserts spaces in textarea instead of focusing next element
|
||||
var ed = document.getElementById('editor');
|
||||
if (ed) {
|
||||
ed.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
var s = ed.selectionStart, en = ed.selectionEnd;
|
||||
ed.value = ed.value.substring(0, s) + ' ' + ed.value.substring(en);
|
||||
ed.selectionStart = ed.selectionEnd = s + 2;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -0,0 +1,158 @@
|
||||
{{template "base" .}}
|
||||
{{define "content"}}
|
||||
<div style="max-width:1400px">
|
||||
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
||||
<div>
|
||||
<div class="page-title">Countries</div>
|
||||
<div class="page-sub">Country-scope bans managed via cscli decisions</div>
|
||||
</div>
|
||||
{{if .CLIAvailable}}<button class="btn-primary" onclick="toggleAddForm()">Add Country Ban</button>{{end}}
|
||||
</div>
|
||||
|
||||
{{if not .CLIAvailable}}
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-body">
|
||||
<div class="empty-text">cscli not available</div>
|
||||
<div class="empty-sub">Country management requires the cscli binary. Set cscli_path in app_config.conf.</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<div id="add-form" class="panel" style="display:none;margin-bottom:16px">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Add Country Ban</span>
|
||||
<button class="btn-ghost-sm" onclick="toggleAddForm()">Cancel</button>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="/countries/add">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<div style="display:grid;grid-template-columns:1fr 160px 160px;gap:16px;margin-bottom:16px">
|
||||
<div>
|
||||
<label class="field-label">Country Codes (ISO 3166-1 alpha-2)</label>
|
||||
<textarea class="field-input" name="countries" rows="3" placeholder="CN RU KP" required autofocus
|
||||
style="resize:vertical;font-family:'JetBrains Mono',monospace;text-transform:uppercase"></textarea>
|
||||
<div class="field-hint">One per line or comma-separated. e.g. CN, RU, US</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label">Decision Type</label>
|
||||
<select class="field-input" name="type">
|
||||
<option value="ban">ban</option>
|
||||
<option value="captcha">captcha</option>
|
||||
<option value="throttle">throttle</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label">Duration</label>
|
||||
<input class="field-input" type="text" name="duration" id="dur-input" placeholder="24h" value="24h">
|
||||
<div class="field-hint">Units: s m h d w</div>
|
||||
<label style="display:flex;align-items:center;gap:6px;margin-top:8px;font-size:12px;cursor:pointer">
|
||||
<input type="checkbox" name="permanent" value="1" id="chk-perm" onchange="togglePermanent(this)">
|
||||
Permanent (10 yr)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end">
|
||||
<button type="submit" class="btn-primary">Add Decision</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<form method="POST" action="/countries/delete" id="bulk-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Active Country Bans — Page {{.Page}}{{if .HasNext}} (more available){{end}}</span>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button type="button" class="btn-ghost-sm" onclick="toggleAll()">Select all</button>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm-fn="confirmBulkDelete">Delete selected</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .Decisions}}
|
||||
<div style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:36px"><input type="checkbox" id="chk-all" onchange="selectAll(this)"></th>
|
||||
<th>Country</th>
|
||||
<th>Type</th>
|
||||
<th>Origin</th>
|
||||
<th>Duration</th>
|
||||
<th>Expires</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Decisions}}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="id" value="{{.ID}}" class="row-chk"></td>
|
||||
<td style="font-size:14px">
|
||||
{{countryFlag .Value}} <span style="font-family:'JetBrains Mono',monospace">{{.Value}}</span>
|
||||
</td>
|
||||
<td><span class="badge {{decisionBadgeClass .Type}}">{{.Type}}</span></td>
|
||||
<td><span class="badge {{originBadgeClass .Origin}}">{{.Origin}}</span></td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">{{.Duration}}</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{if .Until}}{{truncate .Until 16}}{{else}}{{.Duration}}{{end}}</td>
|
||||
<td>
|
||||
<form method="POST" action="/countries/delete" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
<button type="submit" class="btn-danger-sm" data-confirm="Remove ban for {{.Value}}?">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<div class="empty-text">No country bans active</div>
|
||||
<div class="empty-sub">No country-scope decisions are in effect</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</form>
|
||||
|
||||
{{if or (gt .Page 1) .HasNext}}
|
||||
<div class="pagination">
|
||||
{{if gt .Page 1}}
|
||||
<a href="/countries?page={{dec .Page}}" class="btn-ghost-sm">Prev</a>
|
||||
{{end}}
|
||||
<span style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--muted)">Page {{.Page}}</span>
|
||||
{{if .HasNext}}
|
||||
<a href="/countries?page={{inc .Page}}" class="btn-ghost-sm">Next</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
function togglePermanent(cb) {
|
||||
var dur = document.getElementById('dur-input');
|
||||
if (dur) { dur.disabled = cb.checked; dur.value = cb.checked ? '87600h' : '24h'; }
|
||||
}
|
||||
function toggleAddForm() {
|
||||
var f = document.getElementById('add-form');
|
||||
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
function selectAll(cb) {
|
||||
document.querySelectorAll('.row-chk').forEach(function(c) { c.checked = cb.checked; });
|
||||
}
|
||||
function toggleAll() {
|
||||
var cb = document.getElementById('chk-all');
|
||||
cb.checked = !cb.checked;
|
||||
selectAll(cb);
|
||||
}
|
||||
function confirmBulkDelete() {
|
||||
var n = document.querySelectorAll('.row-chk:checked').length;
|
||||
if (n === 0) { appModal.info('Select at least one entry.'); return null; }
|
||||
return 'Remove ' + n + ' country ban(s)?';
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -87,7 +87,7 @@
|
||||
</span>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<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>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm-fn="confirmBulkDelete">Delete selected</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -111,7 +111,9 @@
|
||||
{{range .Decisions}}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="id" value="{{.ID}}" class="row-chk"></td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">{{.Value}}</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">
|
||||
{{if eq .Scope "Country"}}{{countryFlag .Value}} {{end}}{{.Value}}
|
||||
</td>
|
||||
<td><span class="badge {{decisionBadgeClass .Type}}">{{.Type}}</span></td>
|
||||
<td><span class="badge badge-gray">{{.Scope}}</span></td>
|
||||
<td><span class="badge {{originBadgeClass .Origin}}">{{.Origin}}</span></td>
|
||||
@@ -122,7 +124,7 @@
|
||||
<form method="POST" action="/decisions/delete" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
<button type="submit" class="btn-danger-sm" onclick="return confirm('Delete this decision?')">Delete</button>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm="Delete this decision?">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -169,8 +171,8 @@ function toggleAll() {
|
||||
}
|
||||
function confirmBulkDelete() {
|
||||
var n = document.querySelectorAll('.row-chk:checked').length;
|
||||
if (n === 0) { alert('Select at least one decision.'); return false; }
|
||||
return confirm('Delete ' + n + ' decision(s)?');
|
||||
if (n === 0) { appModal.info('Select at least one decision.'); return null; }
|
||||
return 'Delete ' + n + ' decision(s)? This cannot be undone.';
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
{{template "base" .}}
|
||||
{{define "content"}}
|
||||
<div style="max-width:900px">
|
||||
|
||||
<div style="margin-bottom:16px">
|
||||
<div class="page-title">GeoIP Database</div>
|
||||
<div class="page-sub">Automatic download and refresh of ipinfo.io MMDB database for CrowdSec enrichment</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-header"><span class="panel-title">Database Status</span></div>
|
||||
<div class="panel-body">
|
||||
<table style="width:100%;border-collapse:collapse">
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);width:180px;font-size:13px">File</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:13px">{{.Status.DBFile}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">Destination path</td>
|
||||
<td style="font-family:'JetBrains Mono',monospace;font-size:13px">{{.Status.DBPath}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">DB exists</td>
|
||||
<td>
|
||||
{{if .Status.DBExists}}
|
||||
<span class="badge badge-green">Yes</span>
|
||||
<span style="font-size:12px;color:var(--muted);margin-left:8px">{{.Status.DBSizeHuman}}</span>
|
||||
{{else}}
|
||||
<span class="badge badge-red">No</span>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">Last updated</td>
|
||||
<td style="font-size:13px">
|
||||
{{if .Status.LastUpdated.IsZero}}
|
||||
<span style="color:var(--muted)">Never</span>
|
||||
{{else}}
|
||||
{{.Status.LastUpdated.Format "2006-01-02 15:04:05 UTC"}}
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">Next refresh</td>
|
||||
<td style="font-size:13px">
|
||||
{{if .Status.NextRefresh.IsZero}}
|
||||
<span style="color:var(--muted)">—</span>
|
||||
{{else}}
|
||||
{{.Status.NextRefresh.Format "2006-01-02 15:04:05 UTC"}}
|
||||
<span style="color:var(--muted);font-size:12px;margin-left:8px">(every {{.Status.RefreshDays}} days)</span>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">Token configured</td>
|
||||
<td>
|
||||
{{if .Status.TokenSet}}
|
||||
<span class="badge badge-green">Yes</span>
|
||||
{{else}}
|
||||
<span class="badge badge-red">No</span>
|
||||
<span style="font-size:12px;color:var(--muted);margin-left:8px">Set ipinfo_token in app_config.conf</span>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{if .Status.Updating}}
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">Status</td>
|
||||
<td><span class="badge badge-amber">Downloading...</span></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if .Status.LastErrMsg}}
|
||||
<tr>
|
||||
<td style="padding:8px 0;color:var(--muted);font-size:13px">Last error</td>
|
||||
<td style="font-size:12px;color:var(--threat)">{{.Status.LastErrMsg}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .Status.TokenSet}}
|
||||
<div class="panel" style="margin-bottom:16px">
|
||||
<div class="panel-header"><span class="panel-title">Manual Refresh</span></div>
|
||||
<div class="panel-body">
|
||||
<p style="font-size:13px;color:var(--muted);margin:0 0 16px 0">
|
||||
Downloads <strong style="color:var(--text)">{{.Status.DBFile}}</strong> from ipinfo.io
|
||||
and saves it to <code style="font-family:'JetBrains Mono',monospace">{{.Status.DBPath}}</code>.
|
||||
CrowdSec will use the new file automatically — no restart needed.
|
||||
</p>
|
||||
<form method="POST" action="/geoip/refresh">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn-primary"
|
||||
{{if .Status.Updating}}disabled{{end}}
|
||||
data-confirm="Download {{.Status.DBFile}} from ipinfo.io now?" data-confirm-label="Download">
|
||||
{{if .Status.Updating}}Downloading...{{else}}Refresh Now{{end}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-header"><span class="panel-title">Setup Notes</span></div>
|
||||
<div class="panel-body" style="font-size:13px;color:var(--muted);line-height:1.7">
|
||||
<p>1. Get a free token at <strong style="color:var(--text)">ipinfo.io/signup</strong></p>
|
||||
<p>2. Add to <code style="font-family:'JetBrains Mono',monospace">app_config.conf</code>:</p>
|
||||
<pre style="background:var(--surface);border:1px solid var(--border);padding:12px;border-radius:4px;font-size:12px;margin:8px 0 16px">ipinfo_token = your-token-here
|
||||
ipinfo_db_file = asn.mmdb
|
||||
ipinfo_db_path = /var/lib/crowdsec/data/GeoLite2-ASN.mmdb
|
||||
ipinfo_refresh_days = 7</pre>
|
||||
<p>3. Restart crowdsec-dashy and click <strong style="color:var(--text)">Refresh Now</strong></p>
|
||||
<p>4. For country enrichment, also download <code style="font-family:'JetBrains Mono',monospace">country.mmdb</code> and configure CrowdSec to use it</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}{{end}}
|
||||
@@ -11,7 +11,7 @@
|
||||
<form method="POST" action="/hub/update">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn-secondary"
|
||||
onclick="return confirm('Run hub update + upgrade? This may take a minute.')">Update All</button>
|
||||
data-confirm="Run hub update + upgrade? This may take a minute." data-confirm-label="Update">Update All</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -74,7 +74,7 @@
|
||||
<input type="hidden" name="kind" value="{{$tab}}">
|
||||
<input type="hidden" name="tab" value="{{$tab}}">
|
||||
<input type="hidden" name="name" value="{{.Name}}">
|
||||
<button type="submit" class="btn-danger-sm" onclick="return confirm('Remove {{.Name}}?')">Remove</button>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm="Remove {{.Name}}?">Remove</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<form method="POST" action="/hub/install" style="display:inline">
|
||||
|
||||
@@ -55,13 +55,13 @@
|
||||
<form method="POST" action="/machines/validate" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="id" value="{{.MachineID}}">
|
||||
<button type="submit" class="btn-safe-sm" onclick="return confirm('Validate machine "{{.MachineID}}"?')">Validate</button>
|
||||
<button type="submit" class="btn-safe-sm" data-confirm="Validate machine {{.MachineID}}?">Validate</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form method="POST" action="/machines/delete" style="display:inline">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
|
||||
<input type="hidden" name="id" value="{{.MachineID}}">
|
||||
<button type="submit" class="btn-danger-sm" onclick="return confirm('Delete machine "{{.MachineID}}"?')">Delete</button>
|
||||
<button type="submit" class="btn-danger-sm" data-confirm="Delete machine {{.MachineID}}?">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user