restructure the main html-file for better understanding

This commit is contained in:
Michael Reber
2025-01-26 20:11:06 +01:00
parent c02423ac08
commit 75a1ef9a24

View File

@@ -1,7 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Fail2ban UI Dashboard</title> <title>Fail2ban UI Dashboard</title>
<!-- Bootstrap 5 (CDN) --> <!-- Bootstrap 5 (CDN) -->
<link <link
@@ -30,8 +32,11 @@
} }
</style> </style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
<!-- NavBar --> <!-- ******************************************************************* -->
<!-- NAVIGATION : -->
<!-- ******************************************************************* -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#"> <a class="navbar-brand" href="#">
@@ -57,7 +62,8 @@
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
<!-- ******************************************************************* -->
<!-- Reload Banner --> <!-- Reload Banner -->
<div id="reloadBanner" class="bg-warning text-dark p-3 text-center"> <div id="reloadBanner" class="bg-warning text-dark p-3 text-center">
@@ -67,61 +73,67 @@
</button> </button>
</div> </div>
<!-- ******************************************************************* -->
<!-- APP Sections (Pages) : -->
<!-- ******************************************************************* -->
<!-- Dashboard Section --> <!-- Dashboard Section -->
<div id="dashboardSection" class="container my-4"> <div id="dashboardSection" class="container my-4">
<h1 class="mb-4">Dashboard</h1> <h1 class="mb-4">Dashboard</h1>
<div id="dashboard"></div> <div id="dashboard"></div>
</div> </div>
<!-- Filter Debug Section -->
<div id="filterSection" style="display: none;" class="container my-4">
<h2>Filter Debug</h2>
<!-- Dropdown of available jail/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" disabled></textarea>
</div>
<button class="btn btn-secondary" onclick="testSelectedFilter()">Test Filter</button>
<hr/>
<div id="testResults"></div>
</div>
<!-- Settings Section --> <!-- Settings Section -->
<div id="settingsSection" style="display: none;" class="container my-4"> <div id="settingsSection" style="display: none;" class="container my-4">
<h2>Settings</h2> <h2>Settings</h2>
<form onsubmit="saveSettings(event)"> <form onsubmit="saveSettings(event)">
<div class="mb-3"> <div class="mb-3">
<label for="languageSelect" class="form-label">Language</label> <label for="languageSelect" class="form-label">Language</label>
<select id="languageSelect" class="form-select"> <select id="languageSelect" class="form-select" disabled>
<option value="en">English</option> <option value="en">English</option>
<option value="de">Deutsch</option> <option value="de">Deutsch</option>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="alertEmail" class="form-label">Alert Email</label> <label for="alertEmail" class="form-label">Alert Email</label>
<input type="email" class="form-control" id="alertEmail"/> <input type="email" class="form-control" id="alertEmail"/>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Alert Countries</label> <label for="alertCountries" class="form-label">Select alert Countries</label>
<small class="text-muted">(Choose which country bans trigger an email. "all" for everything.)</small> <p class="text-muted">Choose which country-IP bans should trigger an email. With CTRL, you can select multiple.</p>
<input type="text" class="form-control" id="alertCountries" <select id="alertCountries" class="form-select" multiple size="7">
placeholder="e.g. all or DE,CH"/> <option value="ALL">ALL (Every Country)</option>
<option value="CH">Switzerland (CH)</option>
<option value="DE">Germany (DE)</option>
<option value="IT">Italy (IT)</option>
<option value="FR">France (FR)</option>
<option value="UK">England (UK)</option>
<option value="US">United States (US)</option>
<!-- Maybe i will add more later.. -->
</select>
</div> </div>
<button type="submit" class="btn btn-primary">Save</button> <button type="submit" class="btn btn-primary">Save</button>
</form> </form>
</div> </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>
<!-- Footer --> <!-- Footer -->
<footer class="text-center mt-4 mb-4"> <footer class="text-center mt-4 mb-4">
@@ -134,6 +146,9 @@
</p> </p>
</footer> </footer>
<!-- ******************************************************************* -->
<!-- APP Components (HTML) : -->
<!-- ******************************************************************* -->
<!-- Loading Overlay --> <!-- Loading Overlay -->
<div id="loading-overlay" class="d-flex"> <div id="loading-overlay" class="d-flex">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
@@ -165,6 +180,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- ******************************************************************* -->
<!-- Bootstrap 5 JS (for modal, etc.) --> <!-- Bootstrap 5 JS (for modal, etc.) -->
<script <script
@@ -172,11 +188,22 @@
</script> </script>
<script> <script>
// For information: We avoid ES6 backticks in our JS, to prevent confusion with the Go template parser.
"use strict"; "use strict";
// We avoid ES6 backticks here to prevent confusion with the Go template parser. //*******************************************************************
//* Init page and main-components : *
//*******************************************************************
// Init and run first function, when DOM is ready
var currentJailForConfig = null; var currentJailForConfig = null;
window.addEventListener('DOMContentLoaded', function() {
showLoading(true);
checkReloadNeeded();
fetchSummary().then(function() {
showLoading(false);
});
});
// Toggle the loading overlay (with !important) // Toggle the loading overlay (with !important)
function showLoading(show) { function showLoading(show) {
@@ -188,12 +215,43 @@ function showLoading(show) {
} }
} }
window.addEventListener('DOMContentLoaded', function() { // Check if there is still a reload of the fail2ban service needed
showLoading(true); function checkReloadNeeded() {
fetchSummary().then(function() { fetch('/api/settings')
showLoading(false); .then(res => res.json())
}); .then(data => {
}); if (data.reloadNeeded) {
document.getElementById('reloadBanner').style.display = 'block';
} else {
document.getElementById('reloadBanner').style.display = 'none';
}
})
.catch(err => console.error('Error checking reloadNeeded:', err));
}
// Load dynamically the other pages when navigating in nav
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();
}
}
//*******************************************************************
//* Fetch data and render dashboard : *
//*******************************************************************
// Fetch summary (jails, stats, last 5 bans) // Fetch summary (jails, stats, last 5 bans)
function fetchSummary() { function fetchSummary() {
@@ -228,7 +286,7 @@ function renderDashboard(data) {
+ ' <tr>' + ' <tr>'
+ ' <th>Jail Name</th>' + ' <th>Jail Name</th>'
+ ' <th>Total Banned</th>' + ' <th>Total Banned</th>'
+ ' <th>New in Last Hour</th>' + ' <th>New Last Hour</th>'
+ ' <th>Banned IPs (Unban)</th>' + ' <th>Banned IPs (Unban)</th>'
+ ' </tr>' + ' </tr>'
+ ' </thead>' + ' </thead>'
@@ -305,6 +363,10 @@ function renderBannedIPs(jailName, ips) {
return content; return content;
} }
//*******************************************************************
//* Functions to manage IP-bans : *
//*******************************************************************
// Unban IP // Unban IP
function unbanIP(jail, ip) { function unbanIP(jail, ip) {
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) { if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
@@ -317,7 +379,7 @@ function unbanIP(jail, ip) {
if (data.error) { if (data.error) {
alert("Error: " + data.error); alert("Error: " + data.error);
} else { } else {
alert(data.message || "IP unbanned"); alert(data.message || "IP unbanned successfully");
} }
return fetchSummary(); return fetchSummary();
}) })
@@ -329,7 +391,11 @@ function unbanIP(jail, ip) {
}); });
} }
// Open the jail config modal //*******************************************************************
//* Filter-mod and config-mod actions : *
//*******************************************************************
// Open jail/filter modal and load filter-config
function openJailConfigModal(jailName) { function openJailConfigModal(jailName) {
currentJailForConfig = jailName; currentJailForConfig = jailName;
var textArea = document.getElementById('jailConfigTextarea'); var textArea = document.getElementById('jailConfigTextarea');
@@ -358,7 +424,7 @@ function openJailConfigModal(jailName) {
}); });
} }
// Save jail config // Save filter config for the current opened jail
function saveJailConfig() { function saveJailConfig() {
if (!currentJailForConfig) return; if (!currentJailForConfig) return;
showLoading(true); showLoading(true);
@@ -374,7 +440,8 @@ function saveJailConfig() {
if (data.error) { if (data.error) {
alert("Error saving config: " + data.error); alert("Error saving config: " + data.error);
} else { } else {
alert(data.message || "Config saved"); //alert(data.message || "Config saved");
console.log("Filter saved successfully. Reload needed? " + data.reloadNeeded);
// Hide modal // Hide modal
var modalEl = document.getElementById('jailConfigModal'); var modalEl = document.getElementById('jailConfigModal');
var modalObj = bootstrap.Modal.getInstance(modalEl); var modalObj = bootstrap.Modal.getInstance(modalEl);
@@ -391,44 +458,32 @@ function saveJailConfig() {
}); });
} }
// Reload Fail2ban // Load current settings when opening settings page
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);
});
}
function loadSettings() { function loadSettings() {
showLoading(true); showLoading(true);
fetch('/api/settings') fetch('/api/settings')
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
// populate the form // populate language, email, etc...
document.getElementById('languageSelect').value = data.language || 'en'; document.getElementById('languageSelect').value = data.language || 'en';
document.getElementById('alertEmail').value = data.alertEmail || ''; document.getElementById('alertEmail').value = data.sender || '';
if (data.alertCountries && data.alertCountries.length > 0) {
if (data.alertCountries[0] === 'all') { // alertCountries multi
document.getElementById('alertCountries').value = 'all'; const select = document.getElementById('alertCountries');
} else { // clear selection
document.getElementById('alertCountries').value = data.alertCountries.join(','); for (let i = 0; i < select.options.length; i++) {
select.options[i].selected = false;
}
if (!data.alertCountries || data.alertCountries.length === 0) {
// default to "ALL"
select.options[0].selected = true;
} else {
// Mark them selected
for (let i = 0; i < select.options.length; i++) {
let val = select.options[i].value;
if (data.alertCountries.includes(val)) {
select.options[i].selected = true;
}
} }
} }
}) })
@@ -438,25 +493,30 @@ function loadSettings() {
.finally(() => showLoading(false)); .finally(() => showLoading(false));
} }
// Save settings when hit the save button
function saveSettings(e) { function saveSettings(e) {
e.preventDefault(); // prevent form submission e.preventDefault(); // prevent form submission
showLoading(true); showLoading(true);
const lang = document.getElementById('languageSelect').value; const lang = document.getElementById('languageSelect').value;
const mail = document.getElementById('alertEmail').value; const mail = document.getElementById('alertEmail').value;
const countries = document.getElementById('alertCountries').value;
let countryList = []; const select = document.getElementById('alertCountries');
if (!countries || countries.trim() === '') { let chosenCountries = [];
countryList.push('all'); for (let i = 0; i < select.options.length; i++) {
} else { if (select.options[i].selected) {
countryList = countries.split(',').map(s => s.trim()); chosenCountries.push(select.options[i].value);
}
}
// If user selected "ALL", we override everything
if (chosenCountries.includes("ALL")) {
chosenCountries = ["all"];
} }
const body = { const body = {
language: lang, language: lang,
alertEmail: mail, sender: mail,
alertCountries: countryList alertCountries: chosenCountries
}; };
fetch('/api/settings', { fetch('/api/settings', {
@@ -464,41 +524,20 @@ function saveSettings(e) {
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body) body: JSON.stringify(body)
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.error) { if (data.error) {
alert('Error saving settings: ' + data.error); alert('Error saving settings: ' + data.error);
} else { } else {
alert(data.message || 'Settings saved'); //alert(data.message || 'Settings saved');
if (data.needsRestart) { console.log("Settings saved successfully. Reload needed? " + data.reloadNeeded);
// show the same "reload" banner used for filter changes if (data.reloadNeeded) {
document.getElementById('reloadBanner').style.display = 'block'; document.getElementById('reloadBanner').style.display = 'block';
}
} }
} })
}) .catch(err => alert('Error: ' + err))
.catch(err => { .finally(() => showLoading(false));
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 // Load the list of filters from /api/filters
@@ -589,6 +628,35 @@ function showFilterSection() {
document.getElementById('logLinesTextarea').value = ''; document.getElementById('logLinesTextarea').value = '';
} }
//*******************************************************************
//* Reload fail2ban action : *
//*******************************************************************
// Reload Fail2ban
function reloadFail2ban() {
if (!confirm("It can happen that some logs are not parsed during the reload of fail2ban. 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);
});
}
</script> </script>
</body> </body>
</html> </html>