mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
restructure the main html-file for better understanding
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||
<title>Fail2ban UI Dashboard</title>
|
||||
<!-- Bootstrap 5 (CDN) -->
|
||||
<link
|
||||
@@ -30,8 +32,11 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-light">
|
||||
<!-- NavBar -->
|
||||
<!-- ******************************************************************* -->
|
||||
<!-- NAVIGATION : -->
|
||||
<!-- ******************************************************************* -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">
|
||||
@@ -57,7 +62,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
<!-- ******************************************************************* -->
|
||||
|
||||
<!-- Reload Banner -->
|
||||
<div id="reloadBanner" class="bg-warning text-dark p-3 text-center">
|
||||
@@ -67,61 +73,67 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ******************************************************************* -->
|
||||
<!-- APP Sections (Pages) : -->
|
||||
<!-- ******************************************************************* -->
|
||||
|
||||
<!-- Dashboard Section -->
|
||||
<div id="dashboardSection" class="container my-4">
|
||||
<h1 class="mb-4">Dashboard</h1>
|
||||
<div id="dashboard"></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 -->
|
||||
<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">
|
||||
<select id="languageSelect" class="form-select" disabled>
|
||||
<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"/>
|
||||
<label for="alertCountries" class="form-label">Select alert Countries</label>
|
||||
<p class="text-muted">Choose which country-IP bans should trigger an email. With CTRL, you can select multiple.</p>
|
||||
<select id="alertCountries" class="form-select" multiple size="7">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<!-- ******************************************************************* -->
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="text-center mt-4 mb-4">
|
||||
@@ -134,6 +146,9 @@
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<!-- ******************************************************************* -->
|
||||
<!-- APP Components (HTML) : -->
|
||||
<!-- ******************************************************************* -->
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loading-overlay" class="d-flex">
|
||||
<div class="spinner-border text-light" role="status">
|
||||
@@ -165,6 +180,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ******************************************************************* -->
|
||||
|
||||
<!-- Bootstrap 5 JS (for modal, etc.) -->
|
||||
<script
|
||||
@@ -172,11 +188,22 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// For information: We avoid ES6 backticks in our JS, to prevent confusion with the Go template parser.
|
||||
"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;
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
showLoading(true);
|
||||
checkReloadNeeded();
|
||||
fetchSummary().then(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle the loading overlay (with !important)
|
||||
function showLoading(show) {
|
||||
@@ -188,12 +215,43 @@ function showLoading(show) {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
showLoading(true);
|
||||
fetchSummary().then(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
});
|
||||
// Check if there is still a reload of the fail2ban service needed
|
||||
function checkReloadNeeded() {
|
||||
fetch('/api/settings')
|
||||
.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)
|
||||
function fetchSummary() {
|
||||
@@ -228,7 +286,7 @@ function renderDashboard(data) {
|
||||
+ ' <tr>'
|
||||
+ ' <th>Jail Name</th>'
|
||||
+ ' <th>Total Banned</th>'
|
||||
+ ' <th>New in Last Hour</th>'
|
||||
+ ' <th>New Last Hour</th>'
|
||||
+ ' <th>Banned IPs (Unban)</th>'
|
||||
+ ' </tr>'
|
||||
+ ' </thead>'
|
||||
@@ -305,6 +363,10 @@ function renderBannedIPs(jailName, ips) {
|
||||
return content;
|
||||
}
|
||||
|
||||
//*******************************************************************
|
||||
//* Functions to manage IP-bans : *
|
||||
//*******************************************************************
|
||||
|
||||
// Unban IP
|
||||
function unbanIP(jail, ip) {
|
||||
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
|
||||
@@ -317,7 +379,7 @@ function unbanIP(jail, ip) {
|
||||
if (data.error) {
|
||||
alert("Error: " + data.error);
|
||||
} else {
|
||||
alert(data.message || "IP unbanned");
|
||||
alert(data.message || "IP unbanned successfully");
|
||||
}
|
||||
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) {
|
||||
currentJailForConfig = jailName;
|
||||
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() {
|
||||
if (!currentJailForConfig) return;
|
||||
showLoading(true);
|
||||
@@ -374,7 +440,8 @@ function saveJailConfig() {
|
||||
if (data.error) {
|
||||
alert("Error saving config: " + data.error);
|
||||
} else {
|
||||
alert(data.message || "Config saved");
|
||||
//alert(data.message || "Config saved");
|
||||
console.log("Filter saved successfully. Reload needed? " + data.reloadNeeded);
|
||||
// Hide modal
|
||||
var modalEl = document.getElementById('jailConfigModal');
|
||||
var modalObj = bootstrap.Modal.getInstance(modalEl);
|
||||
@@ -391,44 +458,32 @@ function saveJailConfig() {
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
// Load current settings when opening settings page
|
||||
function loadSettings() {
|
||||
showLoading(true);
|
||||
fetch('/api/settings')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
// populate the form
|
||||
// populate language, email, etc...
|
||||
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(',');
|
||||
document.getElementById('alertEmail').value = data.sender || '';
|
||||
|
||||
// alertCountries multi
|
||||
const select = document.getElementById('alertCountries');
|
||||
// clear selection
|
||||
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));
|
||||
}
|
||||
|
||||
// Save settings when hit the save button
|
||||
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 select = document.getElementById('alertCountries');
|
||||
let chosenCountries = [];
|
||||
for (let i = 0; i < select.options.length; i++) {
|
||||
if (select.options[i].selected) {
|
||||
chosenCountries.push(select.options[i].value);
|
||||
}
|
||||
}
|
||||
// If user selected "ALL", we override everything
|
||||
if (chosenCountries.includes("ALL")) {
|
||||
chosenCountries = ["all"];
|
||||
}
|
||||
|
||||
const body = {
|
||||
language: lang,
|
||||
alertEmail: mail,
|
||||
alertCountries: countryList
|
||||
sender: mail,
|
||||
alertCountries: chosenCountries
|
||||
};
|
||||
|
||||
fetch('/api/settings', {
|
||||
@@ -464,41 +524,20 @@ function saveSettings(e) {
|
||||
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';
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert('Error saving settings: ' + data.error);
|
||||
} else {
|
||||
//alert(data.message || 'Settings saved');
|
||||
console.log("Settings saved successfully. Reload needed? " + data.reloadNeeded);
|
||||
if (data.reloadNeeded) {
|
||||
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();
|
||||
}
|
||||
})
|
||||
.catch(err => alert('Error: ' + err))
|
||||
.finally(() => showLoading(false));
|
||||
}
|
||||
|
||||
// Load the list of filters from /api/filters
|
||||
@@ -589,6 +628,35 @@ function showFilterSection() {
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user