Files
Kavosh/app/blueprints/security/templates/security/_content.html
T
2026-08-07 21:17:17 +03:30

126 lines
7.1 KiB
HTML

<div id="security-content"
hx-get="{{ url_for('security.security') }}"
hx-trigger="change from:#security-filter-form"
hx-include="#security-filter-form"
hx-target="#security-content"
hx-swap="outerHTML"
data-from="{{ from_date.isoformat() }}"
data-to="{{ to_date.isoformat() }}"
data-severity="{{ severity }}"
data-rule-type="{{ rule_type }}">
<div class="flex flex-wrap items-end justify-between gap-4 mb-6">
<div>
<h1 class="font-display font-bold text-xl">Suspicious Requests &amp; IP History</h1>
<p class="text-sm text-muted dark:text-muted-dark">Who's poking at your site, and how hard</p>
</div>
<form id="security-filter-form" class="flex flex-wrap gap-3 items-end">
<label class="text-sm text-muted dark:text-muted-dark">From
<input type="date" name="from" value="{{ from_date.isoformat() }}" class="block border border-line dark:border-line-dark rounded-md px-2 py-1 mt-1 bg-surface dark:bg-surface-dark text-ink dark:text-ink-dark font-data text-sm">
</label>
<label class="text-sm text-muted dark:text-muted-dark">To
<input type="date" name="to" value="{{ to_date.isoformat() }}" class="block border border-line dark:border-line-dark rounded-md px-2 py-1 mt-1 bg-surface dark:bg-surface-dark text-ink dark:text-ink-dark font-data text-sm">
</label>
<label class="text-sm text-muted dark:text-muted-dark">Severity
<select name="severity" class="block border border-line dark:border-line-dark rounded-md px-2 py-1 mt-1 bg-surface dark:bg-surface-dark text-ink dark:text-ink-dark text-sm">
<option value="" {{ 'selected' if not severity }}>All</option>
<option value="low" {{ 'selected' if severity == 'low' }}>Low</option>
<option value="medium" {{ 'selected' if severity == 'medium' }}>Medium</option>
<option value="high" {{ 'selected' if severity == 'high' }}>High</option>
</select>
</label>
<label class="text-sm text-muted dark:text-muted-dark">Rule Type
<select name="rule_type" class="block border border-line dark:border-line-dark rounded-md px-2 py-1 mt-1 bg-surface dark:bg-surface-dark text-ink dark:text-ink-dark text-sm">
<option value="" {{ 'selected' if not rule_type }}>All</option>
<option value="sensitive_path" {{ 'selected' if rule_type == 'sensitive_path' }}>Sensitive Path</option>
<option value="injection" {{ 'selected' if rule_type == 'injection' }}>Injection</option>
<option value="scanner_ua" {{ 'selected' if rule_type == 'scanner_ua' }}>Scanner UA</option>
<option value="spoofed_bot" {{ 'selected' if rule_type == 'spoofed_bot' }}>Spoofed Bot</option>
</select>
</label>
</form>
</div>
<div class="mb-6">
<h3 class="text-xs font-medium uppercase tracking-wide text-muted dark:text-muted-dark mb-2">Suspicious events</h3>
<div id="suspicious-events-grid" data-endpoint="{{ url_for('security.api_security_events') }}"></div>
</div>
<div class="mb-6">
<h3 class="text-xs font-medium uppercase tracking-wide text-muted dark:text-muted-dark mb-2">Sensitive-path probes</h3>
<div id="sensitive-paths-panel" class="border border-line dark:border-line-dark rounded-xl bg-surface dark:bg-surface-dark overflow-hidden"
data-endpoint="{{ url_for('security.api_sensitive_paths') }}"></div>
</div>
<div class="mb-6 border border-line dark:border-line-dark rounded-xl p-4 bg-surface dark:bg-surface-dark">
<h3 class="font-display font-semibold text-sm mb-3">Export blocklist</h3>
<div class="flex flex-wrap gap-2 items-center">
<a href="{{ url_for('security.api_export_blocklist') }}"
class="bg-accent dark:bg-accent-dark text-white dark:text-paper-dark rounded-md px-3 py-1.5 text-sm font-medium hover:opacity-90 transition-opacity">Download new (.txt)</a>
<a href="{{ url_for('security.api_export_blocklist', all='true') }}"
class="border border-line dark:border-line-dark rounded-md px-3 py-1.5 text-sm hover:bg-paper dark:hover:bg-paper-dark transition-colors">Re-export all</a>
<select id="blocklist-format" class="border border-line dark:border-line-dark rounded-md px-2 py-1.5 text-sm bg-surface dark:bg-surface-dark text-ink dark:text-ink-dark" onchange="updateBlocklistLinks(this.value)">
<option value="plain">Plain IP list</option>
<option value="htaccess">.htaccess Deny</option>
<option value="iptables">iptables</option>
</select>
</div>
</div>
<div id="ip-history-modal" class="fixed inset-0 bg-ink/40 dark:bg-ink-dark/60 items-center justify-center empty:hidden flex z-50"></div>
<script>
(function initSecurityWidgets() {
const root = document.getElementById('security-content');
const from = root.dataset.from, to = root.dataset.to;
const severity = root.dataset.severity, ruleType = root.dataset.ruleType;
window.initGrid(
'suspicious-events-grid',
document.getElementById('suspicious-events-grid').dataset.endpoint,
[
{ name: 'Timestamp' },
{
name: 'IP',
formatter: (cell) => window.gridHtml(
`<button class="text-accent dark:text-accent-dark underline" hx-get="/api/security/ip/${cell}" hx-target="#ip-history-modal" hx-swap="innerHTML">${cell}</button>`
),
},
{ name: 'Path' }, { name: 'Rule Matched' },
{
name: 'Severity',
formatter: (cell) => {
const tone = { low: 'text-muted dark:text-muted-dark', medium: 'text-warn dark:text-warn-dark', high: 'text-danger dark:text-danger-dark' }[cell] || '';
return window.gridHtml(`<span class="font-medium ${tone}">${cell}</span>`);
},
},
],
{ from, to, severity, rule_type: ruleType },
);
const pathsEl = document.getElementById('sensitive-paths-panel');
fetch(`${pathsEl.dataset.endpoint}?from=${from}&to=${to}`)
.then((r) => r.json())
.then(({ data }) => {
pathsEl.innerHTML = data.length
? `<table class="w-full text-sm font-data">
<thead><tr class="text-left text-muted dark:text-muted-dark text-xs uppercase tracking-wide bg-surface-raised dark:bg-surface-raised-dark font-sans">
<th class="px-3 py-2">Path</th><th class="px-3 py-2">Hits</th><th class="px-3 py-2">Distinct IPs</th>
</tr></thead>
<tbody class="divide-y divide-line dark:divide-line-dark">${
data.map((r) => `<tr><td class="px-3 py-2">${r.path}</td><td class="px-3 py-2">${r.hit_count}</td><td class="px-3 py-2">${r.distinct_ip_count}</td></tr>`).join('')
}</tbody></table>`
: `<p class="text-sm text-muted dark:text-muted-dark p-4">No sensitive-path probes in range.</p>`;
});
window.updateBlocklistLinks = (fmt) => {
document.querySelectorAll('a[href*="export-blocklist"]').forEach((a) => {
const url = new URL(a.href, window.location.origin);
url.searchParams.set('format', fmt);
a.href = url.toString();
});
};
})();
</script>
</div>