2025-01-25 16:21:14 +01:00
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8"/>
|
|
|
|
|
<title>Fail2ban UI Dashboard</title>
|
|
|
|
|
<!-- Bootstrap 5 (CDN) -->
|
|
|
|
|
<link
|
|
|
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
|
|
|
|
|
rel="stylesheet"
|
|
|
|
|
/>
|
|
|
|
|
<style>
|
|
|
|
|
/* Loading overlay styling */
|
|
|
|
|
#loading-overlay {
|
|
|
|
|
display: none; /* hidden by default */
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0; left: 0;
|
|
|
|
|
width: 100%; height: 100%;
|
|
|
|
|
background: rgba(0,0,0,0.5);
|
|
|
|
|
z-index: 9999; /* on top */
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
.spinner-border {
|
|
|
|
|
width: 4rem; height: 4rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reload banner */
|
|
|
|
|
#reloadBanner {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body class="bg-light">
|
|
|
|
|
<!-- NavBar -->
|
|
|
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
2025-01-25 21:43:50 +01:00
|
|
|
<div class="container-fluid">
|
|
|
|
|
<a class="navbar-brand" href="#">
|
|
|
|
|
<strong>Fail2ban UI</strong>
|
|
|
|
|
</a>
|
|
|
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
|
|
|
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
|
|
|
|
|
aria-expanded="false" aria-label="Toggle navigation">
|
|
|
|
|
<span class="navbar-toggler-icon"></span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
|
|
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
|
|
|
|
<li class="nav-item">
|
|
|
|
|
<a class="nav-link" href="#" onclick="showSection('dashboardSection')">Dashboard</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li class="nav-item">
|
|
|
|
|
<a class="nav-link" href="#" onclick="showSection('filterSection')">Filter Debug</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li class="nav-item">
|
|
|
|
|
<a class="nav-link" href="#" onclick="showSection('settingsSection')">Settings</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</nav>
|
2025-01-25 16:21:14 +01:00
|
|
|
|
|
|
|
|
<!-- Reload Banner -->
|
|
|
|
|
<div id="reloadBanner" class="bg-warning text-dark p-3 text-center">
|
|
|
|
|
<strong>Configuration changed! </strong>
|
|
|
|
|
<button class="btn btn-dark" onclick="reloadFail2ban()">
|
|
|
|
|
Reload Fail2ban
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-01-25 21:43:50 +01:00
|
|
|
<!-- Dashboard Section -->
|
|
|
|
|
<div id="dashboardSection" class="container my-4">
|
2025-01-25 16:21:14 +01:00
|
|
|
<h1 class="mb-4">Dashboard</h1>
|
|
|
|
|
<div id="dashboard"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-01-25 21:43:50 +01:00
|
|
|
<!-- Settings Section -->
|
|
|
|
|
<div id="settingsSection" style="display: none;" class="container my-4">
|
|
|
|
|
<h2>Settings</h2>
|
|
|
|
|
<form onsubmit="saveSettings(event)">
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<label for="languageSelect" class="form-label">Language</label>
|
|
|
|
|
<select id="languageSelect" class="form-select">
|
|
|
|
|
<option value="en">English</option>
|
|
|
|
|
<option value="de">Deutsch</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<label for="alertEmail" class="form-label">Alert Email</label>
|
|
|
|
|
<input type="email" class="form-control" id="alertEmail"/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<label class="form-label">Alert Countries</label>
|
|
|
|
|
<small class="text-muted">(Choose which country bans trigger an email. "all" for everything.)</small>
|
|
|
|
|
<input type="text" class="form-control" id="alertCountries"
|
|
|
|
|
placeholder="e.g. all or DE,CH"/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button type="submit" class="btn btn-primary">Save</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Filter Debug Section -->
|
|
|
|
|
<div id="filterSection" style="display: none;" class="container my-4">
|
|
|
|
|
<h2>Filter Debug</h2>
|
|
|
|
|
|
|
|
|
|
<!-- Dropdown of available filters -->
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<label for="filterSelect" class="form-label">Select a Filter</label>
|
|
|
|
|
<select id="filterSelect" class="form-select"></select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Textarea for log lines to test -->
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<label class="form-label">Log Lines</label>
|
|
|
|
|
<textarea id="logLinesTextarea" class="form-control" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button class="btn btn-secondary" onclick="testSelectedFilter()">Test Filter</button>
|
|
|
|
|
<hr/>
|
|
|
|
|
|
|
|
|
|
<div id="testResults"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-01-25 16:21:14 +01:00
|
|
|
<!-- Footer -->
|
|
|
|
|
<footer class="text-center mt-4 mb-4">
|
|
|
|
|
<p class="mb-0">
|
|
|
|
|
© <a href="https://swissmakers.ch" target="_blank">Swissmakers GmbH</a>
|
|
|
|
|
-
|
|
|
|
|
<a href="https://github.com/swissmakers/fail2ban-ui" target="_blank">
|
|
|
|
|
GitHub
|
|
|
|
|
</a>
|
|
|
|
|
</p>
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
<!-- Loading Overlay -->
|
|
|
|
|
<div id="loading-overlay" class="d-flex">
|
|
|
|
|
<div class="spinner-border text-light" role="status">
|
|
|
|
|
<span class="visually-hidden">Loading...</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Jail Config Modal -->
|
|
|
|
|
<div class="modal fade" id="jailConfigModal" tabindex="-1" aria-hidden="true">
|
|
|
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h5 class="modal-title">
|
|
|
|
|
Filter Config: <span id="modalJailName"></span>
|
|
|
|
|
</h5>
|
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
|
|
|
|
aria-label="Close"></button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<textarea id="jailConfigTextarea" class="form-control" rows="15"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
<button type="button" class="btn btn-secondary"
|
|
|
|
|
data-bs-dismiss="modal">Cancel</button>
|
|
|
|
|
<button type="button" class="btn btn-primary" onclick="saveJailConfig()">
|
|
|
|
|
Save
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Bootstrap 5 JS (for modal, etc.) -->
|
|
|
|
|
<script
|
|
|
|
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js">
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// We avoid ES6 backticks here to prevent confusion with the Go template parser.
|
|
|
|
|
|
|
|
|
|
var currentJailForConfig = null;
|
|
|
|
|
|
|
|
|
|
// Toggle the loading overlay (with !important)
|
|
|
|
|
function showLoading(show) {
|
|
|
|
|
var overlay = document.getElementById('loading-overlay');
|
|
|
|
|
if (show) {
|
|
|
|
|
overlay.style.setProperty('display', 'flex', 'important');
|
|
|
|
|
} else {
|
|
|
|
|
overlay.style.setProperty('display', 'none', 'important');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetchSummary().then(function() {
|
|
|
|
|
showLoading(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fetch summary (jails, stats, last 5 bans)
|
|
|
|
|
function fetchSummary() {
|
|
|
|
|
return fetch('/api/summary')
|
|
|
|
|
.then(function(res) { return res.json(); })
|
|
|
|
|
.then(function(data) {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
document.getElementById('dashboard').innerHTML =
|
|
|
|
|
'<div class="alert alert-danger">' + data.error + '</div>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
renderDashboard(data);
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
document.getElementById('dashboard').innerHTML =
|
|
|
|
|
'<div class="alert alert-danger">Error: ' + err + '</div>';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render the main dashboard
|
|
|
|
|
function renderDashboard(data) {
|
|
|
|
|
var html = "";
|
|
|
|
|
|
|
|
|
|
// Jails table
|
|
|
|
|
if (!data.jails || data.jails.length === 0) {
|
|
|
|
|
html += '<p>No jails found.</p>';
|
|
|
|
|
} else {
|
|
|
|
|
html += ''
|
|
|
|
|
+ '<h2>Overview</h2>'
|
|
|
|
|
+ '<table class="table table-striped">'
|
|
|
|
|
+ ' <thead>'
|
|
|
|
|
+ ' <tr>'
|
|
|
|
|
+ ' <th>Jail Name</th>'
|
|
|
|
|
+ ' <th>Total Banned</th>'
|
|
|
|
|
+ ' <th>New in Last Hour</th>'
|
|
|
|
|
+ ' <th>Banned IPs (Unban)</th>'
|
|
|
|
|
+ ' </tr>'
|
|
|
|
|
+ ' </thead>'
|
|
|
|
|
+ ' <tbody>';
|
|
|
|
|
|
|
|
|
|
data.jails.forEach(function(jail) {
|
|
|
|
|
var bannedHTML = renderBannedIPs(jail.jailName, jail.bannedIPs);
|
|
|
|
|
html += ''
|
|
|
|
|
+ '<tr>'
|
|
|
|
|
+ ' <td>'
|
|
|
|
|
+ ' <a href="#" onclick="openJailConfigModal(\'' + jail.jailName + '\')">'
|
|
|
|
|
+ jail.jailName
|
|
|
|
|
+ ' </a>'
|
|
|
|
|
+ ' </td>'
|
|
|
|
|
+ ' <td>' + jail.totalBanned + '</td>'
|
|
|
|
|
+ ' <td>' + jail.newInLastHour + '</td>'
|
|
|
|
|
+ ' <td>' + bannedHTML + '</td>'
|
|
|
|
|
+ '</tr>';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
html += '</tbody></table>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Last 5 bans
|
|
|
|
|
html += '<h2>Last 5 Ban Events</h2>';
|
|
|
|
|
if (!data.lastBans || data.lastBans.length === 0) {
|
|
|
|
|
html += '<p>No recent bans found.</p>';
|
|
|
|
|
} else {
|
|
|
|
|
html += ''
|
|
|
|
|
+ '<table class="table table-bordered">'
|
|
|
|
|
+ ' <thead>'
|
|
|
|
|
+ ' <tr>'
|
|
|
|
|
+ ' <th>Time</th>'
|
|
|
|
|
+ ' <th>Jail</th>'
|
|
|
|
|
+ ' <th>IP</th>'
|
|
|
|
|
+ ' <th>Log Line</th>'
|
|
|
|
|
+ ' </tr>'
|
|
|
|
|
+ ' </thead>'
|
|
|
|
|
+ ' <tbody>';
|
|
|
|
|
|
|
|
|
|
data.lastBans.forEach(function(e) {
|
|
|
|
|
html += ''
|
|
|
|
|
+ '<tr>'
|
|
|
|
|
+ ' <td>' + e.Time + '</td>'
|
|
|
|
|
+ ' <td>' + e.Jail + '</td>'
|
|
|
|
|
+ ' <td>' + e.IP + '</td>'
|
|
|
|
|
+ ' <td>' + e.LogLine + '</td>'
|
|
|
|
|
+ '</tr>';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
html += '</tbody></table>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('dashboard').innerHTML = html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render banned IPs with "Unban" button
|
|
|
|
|
function renderBannedIPs(jailName, ips) {
|
|
|
|
|
if (!ips || ips.length === 0) {
|
|
|
|
|
return '<em>No banned IPs</em>';
|
|
|
|
|
}
|
|
|
|
|
var content = '<ul class="list-unstyled mb-0">';
|
|
|
|
|
ips.forEach(function(ip) {
|
|
|
|
|
content += ''
|
|
|
|
|
+ '<li class="d-flex align-items-center mb-1">'
|
|
|
|
|
+ ' <span class="me-auto">' + ip + '</span>'
|
|
|
|
|
+ ' <button class="btn btn-sm btn-warning"'
|
|
|
|
|
+ ' onclick="unbanIP(\'' + jailName + '\', \'' + ip + '\')">'
|
|
|
|
|
+ ' Unban'
|
|
|
|
|
+ ' </button>'
|
|
|
|
|
+ '</li>';
|
|
|
|
|
});
|
|
|
|
|
content += '</ul>';
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unban IP
|
|
|
|
|
function unbanIP(jail, ip) {
|
|
|
|
|
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetch('/api/jails/' + jail + '/unban/' + ip, { method: 'POST' })
|
|
|
|
|
.then(function(res) { return res.json(); })
|
|
|
|
|
.then(function(data) {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert("Error: " + data.error);
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || "IP unbanned");
|
|
|
|
|
}
|
|
|
|
|
return fetchSummary();
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
alert("Error: " + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(function() {
|
|
|
|
|
showLoading(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open the jail config modal
|
|
|
|
|
function openJailConfigModal(jailName) {
|
|
|
|
|
currentJailForConfig = jailName;
|
|
|
|
|
var textArea = document.getElementById('jailConfigTextarea');
|
|
|
|
|
textArea.value = '';
|
|
|
|
|
|
|
|
|
|
document.getElementById('modalJailName').textContent = jailName;
|
|
|
|
|
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetch('/api/jails/' + jailName + '/config')
|
|
|
|
|
.then(function(res) { return res.json(); })
|
|
|
|
|
.then(function(data) {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert("Error loading config: " + data.error);
|
|
|
|
|
} else {
|
|
|
|
|
textArea.value = data.config;
|
|
|
|
|
var modalEl = document.getElementById('jailConfigModal');
|
|
|
|
|
var myModal = new bootstrap.Modal(modalEl);
|
|
|
|
|
myModal.show();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
alert("Error: " + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(function() {
|
|
|
|
|
showLoading(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save jail config
|
|
|
|
|
function saveJailConfig() {
|
|
|
|
|
if (!currentJailForConfig) return;
|
|
|
|
|
showLoading(true);
|
|
|
|
|
|
|
|
|
|
var newConfig = document.getElementById('jailConfigTextarea').value;
|
|
|
|
|
fetch('/api/jails/' + currentJailForConfig + '/config', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({ config: newConfig }),
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) { return res.json(); })
|
|
|
|
|
.then(function(data) {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert("Error saving config: " + data.error);
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || "Config saved");
|
|
|
|
|
// Hide modal
|
|
|
|
|
var modalEl = document.getElementById('jailConfigModal');
|
|
|
|
|
var modalObj = bootstrap.Modal.getInstance(modalEl);
|
|
|
|
|
modalObj.hide();
|
|
|
|
|
// Show the reload banner
|
|
|
|
|
document.getElementById('reloadBanner').style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
alert("Error: " + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(function() {
|
|
|
|
|
showLoading(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reload Fail2ban
|
|
|
|
|
function reloadFail2ban() {
|
|
|
|
|
if (!confirm("Reload Fail2ban now?")) return;
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetch('/api/fail2ban/reload', { method: 'POST' })
|
|
|
|
|
.then(function(res) { return res.json(); })
|
|
|
|
|
.then(function(data) {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert("Error: " + data.error);
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || "Fail2ban reloaded");
|
|
|
|
|
// Hide reload banner
|
|
|
|
|
document.getElementById('reloadBanner').style.display = 'none';
|
|
|
|
|
// Refresh data
|
|
|
|
|
return fetchSummary();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
alert("Error: " + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(function() {
|
|
|
|
|
showLoading(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-01-25 21:43:50 +01:00
|
|
|
|
|
|
|
|
function loadSettings() {
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetch('/api/settings')
|
|
|
|
|
.then(res => res.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
// populate the form
|
|
|
|
|
document.getElementById('languageSelect').value = data.language || 'en';
|
|
|
|
|
document.getElementById('alertEmail').value = data.alertEmail || '';
|
|
|
|
|
if (data.alertCountries && data.alertCountries.length > 0) {
|
|
|
|
|
if (data.alertCountries[0] === 'all') {
|
|
|
|
|
document.getElementById('alertCountries').value = 'all';
|
|
|
|
|
} else {
|
|
|
|
|
document.getElementById('alertCountries').value = data.alertCountries.join(',');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
alert('Error loading settings: ' + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(() => showLoading(false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveSettings(e) {
|
|
|
|
|
e.preventDefault(); // prevent form submission
|
|
|
|
|
|
|
|
|
|
showLoading(true);
|
|
|
|
|
const lang = document.getElementById('languageSelect').value;
|
|
|
|
|
const mail = document.getElementById('alertEmail').value;
|
|
|
|
|
const countries = document.getElementById('alertCountries').value;
|
|
|
|
|
|
|
|
|
|
let countryList = [];
|
|
|
|
|
if (!countries || countries.trim() === '') {
|
|
|
|
|
countryList.push('all');
|
|
|
|
|
} else {
|
|
|
|
|
countryList = countries.split(',').map(s => s.trim());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = {
|
|
|
|
|
language: lang,
|
|
|
|
|
alertEmail: mail,
|
|
|
|
|
alertCountries: countryList
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetch('/api/settings', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
|
})
|
|
|
|
|
.then(res => res.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert('Error saving settings: ' + data.error);
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || 'Settings saved');
|
|
|
|
|
if (data.needsRestart) {
|
|
|
|
|
// show the same "reload" banner used for filter changes
|
|
|
|
|
document.getElementById('reloadBanner').style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
alert('Error: ' + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(() => showLoading(false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showSection(sectionId) {
|
|
|
|
|
// hide all sections
|
|
|
|
|
document.getElementById('dashboardSection').style.display = 'none';
|
|
|
|
|
document.getElementById('filterSection').style.display = 'none';
|
|
|
|
|
document.getElementById('settingsSection').style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// show the requested section
|
|
|
|
|
document.getElementById(sectionId).style.display = 'block';
|
|
|
|
|
|
|
|
|
|
// If it's filterSection, load filters
|
|
|
|
|
if (sectionId === 'filterSection') {
|
|
|
|
|
showFilterSection();
|
|
|
|
|
}
|
|
|
|
|
// If it's settingsSection, load settings
|
|
|
|
|
if (sectionId === 'settingsSection') {
|
|
|
|
|
loadSettings();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load the list of filters from /api/filters
|
|
|
|
|
function loadFilters() {
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetch('/api/filters')
|
|
|
|
|
.then(res => res.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert('Error loading filters: ' + data.error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const select = document.getElementById('filterSelect');
|
|
|
|
|
select.innerHTML = ''; // clear existing
|
|
|
|
|
if (!data.filters || data.filters.length === 0) {
|
|
|
|
|
// optional fallback
|
|
|
|
|
const opt = document.createElement('option');
|
|
|
|
|
opt.value = '';
|
|
|
|
|
opt.textContent = 'No Filters Found';
|
|
|
|
|
select.appendChild(opt);
|
|
|
|
|
} else {
|
|
|
|
|
data.filters.forEach(f => {
|
|
|
|
|
const opt = document.createElement('option');
|
|
|
|
|
opt.value = f;
|
|
|
|
|
opt.textContent = f;
|
|
|
|
|
select.appendChild(opt);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
alert('Error loading filters: ' + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(() => showLoading(false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called when clicking "Test Filter" button
|
|
|
|
|
function testSelectedFilter() {
|
|
|
|
|
const filterName = document.getElementById('filterSelect').value;
|
|
|
|
|
const lines = document.getElementById('logLinesTextarea').value.split('\n');
|
|
|
|
|
|
|
|
|
|
if (!filterName) {
|
|
|
|
|
alert('Please select a filter.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showLoading(true);
|
|
|
|
|
fetch('/api/filters/test', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
filterName: filterName,
|
|
|
|
|
logLines: lines
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.then(res => res.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (data.error) {
|
|
|
|
|
alert('Error: ' + data.error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// data.matches, for example
|
|
|
|
|
renderTestResults(data.matches);
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
alert('Error: ' + err);
|
|
|
|
|
})
|
|
|
|
|
.finally(() => showLoading(false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderTestResults(matches) {
|
|
|
|
|
let html = '<h5>Test Results</h5>';
|
|
|
|
|
if (!matches || matches.length === 0) {
|
|
|
|
|
html += '<p>No matches found.</p>';
|
|
|
|
|
} else {
|
|
|
|
|
html += '<ul>';
|
|
|
|
|
matches.forEach(m => {
|
|
|
|
|
html += '<li>' + m + '</li>';
|
|
|
|
|
});
|
|
|
|
|
html += '</ul>';
|
|
|
|
|
}
|
|
|
|
|
document.getElementById('testResults').innerHTML = html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When showing the filter section
|
|
|
|
|
function showFilterSection() {
|
|
|
|
|
loadFilters(); // fetch the filter list
|
|
|
|
|
document.getElementById('testResults').innerHTML = '';
|
|
|
|
|
document.getElementById('logLinesTextarea').value = '';
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 16:21:14 +01:00
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|