Restructure an adding basic sections 2/2

This commit is contained in:
2026-02-19 19:24:18 +01:00
parent 2169b9862f
commit 9fe7276073
11 changed files with 1125 additions and 1995 deletions

View File

@@ -1,7 +1,7 @@
<!--
Fail2ban UI - A Swiss made, management interface for Fail2ban.
Copyright (C) 2025 Swissmakers GmbH
Copyright (C) 2026 Swissmakers GmbH
Licensed under the GNU General Public License, Version 3 (GPL-3.0)
You may not use this file except in compliance with the License.
@@ -22,21 +22,14 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title data-i18n="page.title">Fail2ban UI Dashboard</title>
<!-- Prism.js for syntax highlighting -->
<link rel="stylesheet" href="/static/vendor/prism/prism-tomorrow.min.css?v={{.version}}" />
<script src="/static/vendor/prism/prism-core.min.js?v={{.version}}"></script>
<script src="/static/vendor/prism/prism-autoloader.min.js?v={{.version}}"></script>
<!-- Tailwind CSS -->
<link rel="stylesheet" href="/static/tailwind.css?v={{.version}}">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="/static/vendor/fontawesome/all.min.css?v={{.version}}">
<!-- Select2 CSS -->
<link rel="stylesheet" href="/static/vendor/select2/select2.min.css?v={{.version}}" />
<!-- Fail2ban UI CSS -->
<link rel="stylesheet" href="/static/fail2ban-ui.css?v={{.version}}">
<!-- LOTR Theme CSS (loaded conditionally) -->
<link rel="stylesheet" href="/static/lotr.css?v={{.version}}" id="lotr-css" disabled>
<!-- Google Fonts for LOTR theme -->
<link rel="stylesheet" href="/static/vendor/fonts/google-fonts.css?v={{.version}}">
</head>
@@ -80,7 +73,6 @@
<div id="clockDisplay" class="ml-4 text-sm font-mono">
<span id="clockTime">--:--:--</span>
</div>
<!-- User info and logout (shown when authenticated) -->
<div id="userInfoContainer" class="hidden ml-4 flex items-center gap-3 border-l border-blue-500 pl-4">
<div class="relative">
<button id="userMenuButton" onclick="toggleUserMenu()" class="flex items-center gap-2 px-3 py-2 rounded text-sm font-medium hover:bg-blue-700 transition-colors focus:outline-none">
@@ -89,7 +81,6 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
<!-- User dropdown menu -->
<div id="userMenuDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50 border border-gray-200">
<div class="px-4 py-2 border-b border-gray-200">
<div class="text-sm font-medium text-gray-900" id="userMenuDisplayName"></div>
@@ -116,7 +107,6 @@
<a href="#" onclick="showSection('dashboardSection')" class="block px-3 py-2 rounded-md text-base font-medium hover:bg-blue-700 transition-colors" data-i18n="nav.dashboard">Dashboard</a>
<a href="#" onclick="showSection('filterSection')" class="block px-3 py-2 rounded-md text-base font-medium hover:bg-blue-700 transition-colors" data-i18n="nav.filter_debug">Filter Debug</a>
<a href="#" onclick="showSection('settingsSection')" class="block px-3 py-2 rounded-md text-base font-medium hover:bg-blue-700 transition-colors" data-i18n="nav.settings">Settings</a>
<!-- User info and logout in mobile menu (shown when authenticated) -->
<div id="mobileUserInfoContainer" class="hidden border-t border-blue-500 mt-2 pt-2">
<div class="px-3 py-2">
<div class="text-sm font-medium" id="mobileUserDisplayName"></div>
@@ -126,15 +116,17 @@
</div>
</div>
</div>
<!-- Mobile menu END -->
</nav>
<!-- ************************ Navigation END *************************** -->
<!-- Login Page (hidden by default, shown only when OIDC enabled and not authenticated) -->
<!-- ******************************************************************* -->
<!-- Login Page START -->
<!-- ******************************************************************* -->
<div id="loginPage" class="hidden min-h-screen flex items-center justify-center bg-gray-100 py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full">
<!-- Login Card -->
<div class="bg-white rounded-lg shadow-lg p-8 border border-gray-200">
<!-- Logo and Title -->
<div class="text-center mb-8">
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-blue-600 mb-4">
<svg class="h-10 w-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -144,8 +136,6 @@
<h2 class="text-3xl font-bold text-gray-900 mb-2" data-i18n="auth.login_title">Sign in to Fail2ban UI</h2>
<p class="text-sm text-gray-600" data-i18n="auth.login_description">Please authenticate to access the management interface</p>
</div>
<!-- Error Message -->
<div id="loginError" class="hidden bg-red-50 border-l-4 border-red-400 text-red-700 px-4 py-3 rounded mb-6">
<div class="flex">
<div class="flex-shrink-0">
@@ -158,8 +148,6 @@
</div>
</div>
</div>
<!-- Login Button -->
<div class="mb-6">
<button type="button" onclick="handleLogin()" class="w-full flex justify-center items-center py-3 px-4 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
<svg class="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -167,8 +155,6 @@
</svg>
<span data-i18n="auth.login_button">Sign in with OIDC</span>
</button>
<!-- Loading State -->
<div id="loginLoading" class="hidden text-center py-4">
<div class="inline-flex items-center">
<div class="h-5 w-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin mr-3"></div>
@@ -176,8 +162,6 @@
</div>
</div>
</div>
<!-- Footer Info -->
<div class="pt-6 border-t border-gray-200">
<p class="text-xs text-center text-gray-500">
Secure authentication via OpenID Connect
@@ -186,12 +170,14 @@
</div>
</div>
</div>
<!-- ********************** Login Page END ******************************* -->
<!-- Main Content -->
<main id="mainContent" class="hidden max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<!-- ******************************************************************* -->
<!-- Dashboard Page START -->
<!-- ******************************************************************* -->
<!-- ******************************************************************* -->
<!-- Dashboard Section START -->
<!-- ******************************************************************* -->
<div id="dashboardSection">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-6">
<div>
@@ -220,15 +206,14 @@
</div>
</div>
</div>
<div id="dashboard"></div> <!-- Dynamic content from the API -->
<div id="dashboard"></div>
</div>
<!-- ********************** Dashboard Page END ************************* -->
<!-- ********************** Dashboard Section END ********************** -->
<!-- ******************************************************************* -->
<!-- Filter-Debug Page START -->
<!-- ******************************************************************* -->
<!-- ******************************************************************* -->
<!-- Filter-Debug Section START -->
<!-- ******************************************************************* -->
<div id="filterSection" class="hidden">
<h2 class="text-2xl font-bold text-gray-800 mb-6" data-i18n="filter_debug.title">Filter Debug</h2>
@@ -246,8 +231,6 @@
</div>
</div>
</div>
<!-- Textarea for filter content (readonly by default, editable with Edit button) -->
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<label for="filterContentTextarea" class="block text-sm font-medium text-gray-700" data-i18n="filter_debug.filter_content">Filter Content</label>
@@ -258,8 +241,6 @@
<textarea id="filterContentTextarea" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-40 font-mono text-sm bg-gray-50"
placeholder="Filter content will appear here when a filter is selected..." readonly></textarea>
</div>
<!-- Textarea for log lines to test -->
<div class="mb-4">
<label for="logLinesTextarea" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="filter_debug.log_lines">Log Lines</label>
<textarea id="logLinesTextarea" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-40"
@@ -271,21 +252,19 @@
<div id="testResults" class="hidden bg-gray-900 rounded-lg shadow p-6 text-white font-mono text-sm"></div>
</div>
<!-- ********************* Filter-Debug Page END *********************** -->
<!-- ********************* Filter-Debug Page END *********************** -->
<!-- ******************************************************************* -->
<!-- Settings Page START -->
<!-- ******************************************************************* -->
<!-- ******************************************************************* -->
<!-- Settings Page START -->
<!-- ******************************************************************* -->
<div id="settingsSection" class="hidden">
<h2 class="text-2xl font-bold text-gray-800 mb-6" data-i18n="settings.title">Settings</h2>
<form onsubmit="saveSettings(event)" class="space-y-6">
<!-- General Settings Group -->
<!-- ========================= General Settings ========================= -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4" data-i18n="settings.general">General Settings</h3>
<!-- Language Selection -->
<div class="mb-4">
<label for="languageSelect" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.language">Language</label>
<select id="languageSelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
@@ -297,8 +276,6 @@
<option value="de_ch">Schwiizerdütsch</option>
</select>
</div>
<!-- Fail2Ban UI Port (server) -->
<div class="mb-4">
<label for="uiPort" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.server_port">Server Port</label>
<input type="number" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" id="uiPort"
@@ -319,7 +296,6 @@
</p>
<p class="text-xs text-gray-500 mt-1" id="callbackUrlDefaultHint" data-i18n="settings.callback_url_hint">This URL is used by all Fail2Ban instances to send ban alerts back to Fail2Ban UI. For local deployments, use the same port as Fail2Ban UI (e.g., http://127.0.0.1:8080). For reverse proxy setups, use your TLS-encrypted endpoint (e.g., https://fail2ban.example.com).</p>
</div>
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<label for="callbackSecret" class="block text-sm font-medium text-gray-700" data-i18n="settings.callback_secret">Fail2ban Callback URL Secret</label>
@@ -329,8 +305,6 @@
data-i18n-placeholder="settings.callback_secret_placeholder" placeholder="Auto-generated 42-character secret" />
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.callback_secret.description">This secret is automatically generated and used to authenticate ban notification requests. It is included in the fail2ban action configuration.</p>
</div>
<!-- Debug Log Output -->
<div class="flex items-center gap-4 border border-gray-200 rounded-lg p-2 overflow-x-auto bg-gray-50">
<div class="flex items-center">
<input type="checkbox" id="debugMode" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out">
@@ -341,8 +315,6 @@
<label for="consoleOutput" class="ml-2 block text-sm text-gray-700" data-i18n="settings.enable_console">Enable Console Output</label>
</div>
</div>
<!-- Console Output Window -->
<div id="consoleOutputContainer" class="hidden mt-4 border border-gray-700 rounded-lg bg-gray-900 shadow-lg overflow-hidden">
<div class="flex items-center justify-between bg-gray-800 px-4 py-2 border-b border-gray-700">
<div class="flex items-center gap-2">
@@ -361,7 +333,7 @@
</div>
</div>
<!-- Advanced Actions -->
<!-- ========================= Advanced Actions ========================= -->
<div class="bg-white rounded-lg shadow p-6">
<div class="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
<div>
@@ -373,19 +345,16 @@
<button type="button" class="px-3 py-2 text-sm rounded border border-blue-600 text-blue-600 hover:bg-blue-50" onclick="openAdvancedTestModal()" data-i18n="settings.advanced.test_button">Manually Block / Test</button>
</div>
</div>
<div class="mt-4 space-y-4">
<div class="flex items-center">
<input type="checkbox" id="advancedActionsEnabled" class="h-4 w-4 text-blue-600 border-gray-300 rounded">
<label for="advancedActionsEnabled" class="ml-2 text-sm text-gray-700" data-i18n="settings.advanced.enable">Enable automatic permanent blocking</label>
</div>
<div>
<label for="advancedThreshold" class="block text-sm font-medium text-gray-700" data-i18n="settings.advanced.threshold">Threshold before permanent block</label>
<input type="number" id="advancedThreshold" min="1" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="5">
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.advanced.threshold_hint">If an IP is banned at least this many times it will be forwarded to the selected firewall integration.</p>
</div>
<div>
<label for="advancedIntegrationSelect" class="block text-sm font-medium text-gray-700" data-i18n="settings.advanced.integration">Integration</label>
<select id="advancedIntegrationSelect" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
@@ -426,7 +395,6 @@
</div>
</div>
</div>
<div id="advancedPfSenseFields" class="hidden border border-gray-200 rounded-lg p-4 overflow-x-auto bg-gray-50">
<p class="text-sm text-gray-500 mb-3" data-i18n="settings.advanced.pfsense.note">Requires the pfSense REST API package. Enter the API key and alias to manage.</p>
<div class="mb-3 text-sm">
@@ -454,7 +422,6 @@
</div>
</div>
</div>
<div id="advancedOPNsenseFields" class="hidden border border-gray-200 rounded-lg p-4 overflow-x-auto bg-gray-50">
<p class="text-sm text-gray-500 mb-3" data-i18n="settings.advanced.opnsense.note">Enter the OPNsense API credentials and alias to manage.</p>
<div class="mb-3 text-sm">
@@ -486,7 +453,6 @@
</div>
</div>
</div>
<div class="mt-6">
<h4 class="text-md font-semibold text-gray-800 mb-2" data-i18n="settings.advanced.log_title">Permanent Block Log</h4>
<div id="permanentBlockLog" class="overflow-x-auto border border-gray-200 rounded-md">
@@ -495,11 +461,9 @@
</div>
</div>
<!-- Alert Settings Group -->
<!-- ========================= Alert Settings =========================== -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4" data-i18n="settings.alert">Alert Settings</h3>
<!-- Email Alert Preferences -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.email_alerts">Email Alert Preferences</label>
<div class="space-y-2">
@@ -513,15 +477,12 @@
</label>
</div>
</div>
<div class="mb-4" id="emailFieldsContainer">
<label for="destEmail" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.destination_email">Destination Email (Alerts Receiver)</label>
<input type="email" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed" id="destEmail"
data-i18n-placeholder="settings.destination_email_placeholder" placeholder="alerts@swissmakers.ch" />
<p class="text-xs text-red-600 mt-1 hidden" id="destEmailError"></p>
</div>
<!-- GeoIP Provider -->
<div class="mb-4">
<label for="geoipProvider" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.geoip_provider">GeoIP Provider</label>
<select id="geoipProvider" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" onchange="onGeoIPProviderChange(this.value)">
@@ -530,21 +491,16 @@
</select>
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.geoip_provider.description">Choose the GeoIP lookup provider. MaxMind requires a local database file, while Built-in uses a free online API.</p>
</div>
<!-- GeoIP Database Path (shown only for MaxMind) -->
<div id="geoipDatabasePathContainer" class="mb-4">
<label for="geoipDatabasePath" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.geoip_database_path">GeoIP Database Path</label>
<input type="text" id="geoipDatabasePath" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="/usr/share/GeoIP/GeoLite2-Country.mmdb">
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.geoip_database_path.description">Path to the MaxMind GeoLite2-Country database file.</p>
</div>
<!-- Max Log Lines -->
<div class="mb-4">
<label for="maxLogLines" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.max_log_lines">Maximum Log Lines</label>
<input type="number" id="maxLogLines" min="1" max="500" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="50">
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.max_log_lines.description">Maximum number of log lines to include in ban notifications. Most relevant lines are selected automatically.</p>
</div>
<div class="mb-4">
<label for="alertCountries" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.alert_countries">Alert Countries</label>
<p class="text-sm text-gray-500 mb-2" data-i18n="settings.alert_countries_description">
@@ -751,7 +707,7 @@
</div>
</div>
<!-- SMTP Configuration Group -->
<!-- ========================= SMTP Configuration ======================= -->
<div class="bg-white rounded-lg shadow p-6" id="smtpFieldsContainer">
<h3 class="text-lg font-medium text-gray-900 mb-4" data-i18n="settings.smtp">SMTP Configuration</h3>
<div class="mb-4">
@@ -805,12 +761,10 @@
</div>
</div>
<!-- Fail2Ban Configuration Group -->
<!-- ========================= Fail2Ban Defaults ======================== -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4" data-i18n="settings.fail2ban">Global Default Fail2Ban Configurations</h3>
<p class="text-sm text-gray-600 mb-4" data-i18n="settings.fail2ban.description">These settings will be applied to all enabled Fail2Ban servers and stored in their jail.local [DEFAULT] section.</p>
<!-- Bantime Increment -->
<div class="mb-4">
<div class="flex items-center mb-2">
<input type="checkbox" id="bantimeIncrement" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out" />
@@ -818,8 +772,6 @@
</div>
<p class="text-xs text-gray-500 ml-9" data-i18n="settings.enable_bantime_increment.description">If set to true, the bantime will be calculated using the formula: bantime = findtime * (number of failures / maxretry) * (1 + bantime.rndtime).</p>
</div>
<!-- Default Enabled -->
<div class="mb-4">
<div class="flex items-center mb-2">
<input type="checkbox" id="defaultJailEnable" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out" />
@@ -827,8 +779,6 @@
</div>
<p class="text-xs text-gray-500 ml-9" data-i18n="settings.default_jail_enable.description">If enabled, all jails will be enabled by default. When disabled, jails must be explicitly enabled.</p>
</div>
<!-- Bantime -->
<div class="mb-4">
<label for="banTime" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.default_bantime">Default Bantime</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.default_bantime.description">The number of seconds that a host is banned. Time format: 1h = 1 hour, 1d = 1 day, 1w = 1 week, 1m = 1 month, 1y = 1 year.</p>
@@ -836,16 +786,12 @@
data-i18n-placeholder="settings.default_bantime_placeholder" placeholder="e.g., 48h" />
<p class="text-xs text-red-600 mt-1 hidden" id="banTimeError"></p>
</div>
<!-- Bantime Rndtime (optional, for bantime increment formula) -->
<div class="mb-4">
<label for="bantimeRndtime" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.bantime_rndtime">Bantime Rndtime</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.bantime_rndtime.description">Optional. Max random seconds added in bantime increment formula (e.g. 2048). Leave empty to use Fail2ban default.</p>
<input type="text" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" id="bantimeRndtime"
data-i18n-placeholder="settings.bantime_rndtime_placeholder" placeholder="e.g., 2048" />
</div>
<!-- Banaction -->
<div class="mb-4">
<label for="banaction" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.banaction">Banaction</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.banaction.description">Default banning action (e.g. nftables-multiport, nftables-allports, firewallcmd-rich-rules, etc). It is used to define action_* variables.</p>
@@ -881,8 +827,6 @@
<option value="apf">apf</option>
</select>
</div>
<!-- Banaction Allports -->
<div class="mb-4">
<label for="banactionAllports" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.banaction_allports">Banaction Allports</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.banaction_allports.description">Banning action for all ports (e.g. iptables-allports, firewallcmd-allports, etc). Used when a jail needs to ban all ports instead of specific ones.</p>
@@ -918,8 +862,6 @@
<option value="apf">apf</option>
</select>
</div>
<!-- Default Chain -->
<div class="mb-4">
<div class="flex items-center gap-2 mb-2">
<label for="defaultChain" class="block text-sm font-medium text-gray-700" data-i18n="settings.default_chain">Default Chain</label>
@@ -932,8 +874,6 @@
<option value="FORWARD" data-i18n="settings.chain_forward">FORWARD</option>
</select>
</div>
<!-- Findtime -->
<div class="mb-4">
<label for="findTime" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.default_findtime">Default Findtime</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.default_findtime.description">A host is banned if it has generated 'maxretry' failures during the last 'findtime' seconds. Time format: 1h = 1 hour, 1d = 1 day, 1w = 1 week, 1m = 1 month, 1y = 1 year.</p>
@@ -941,8 +881,6 @@
data-i18n-placeholder="settings.default_findtime_placeholder" placeholder="e.g., 30m" />
<p class="text-xs text-red-600 mt-1 hidden" id="findTimeError"></p>
</div>
<!-- Max Retry -->
<div class="mb-4">
<label for="maxRetry" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.default_max_retry">Default Max Retry</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.default_max_retry.description">Number of failures before a host gets banned.</p>
@@ -950,8 +888,6 @@
data-i18n-placeholder="settings.default_max_retry_placeholder" placeholder="Enter maximum retries" min="1" />
<p class="text-xs text-red-600 mt-1 hidden" id="maxRetryError"></p>
</div>
<!-- Ignore IPs -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.ignore_ips">Ignore IPs</label>
<p class="text-xs text-gray-500 mb-2" data-i18n="settings.ignore_ips.description">Space separated list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which matches an address in this list.</p>
@@ -962,16 +898,16 @@
<div id="ignoreIPsError" class="hidden text-red-600 text-sm mt-1"></div>
</div>
</div>
</div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors" data-i18n="settings.save">Save</button>
</form>
</div>
<!-- *********************** Settings Page END ************************* -->
<!-- *********************** Settings Page END ************************* -->
</main>
<!-- Footer -->
<!-- ******************************************************************* -->
<!-- Footer -->
<!-- ******************************************************************* -->
<footer id="footer" class="hidden bg-gray-100 py-4">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-gray-600 text-sm">
<p class="mb-0">
@@ -988,11 +924,12 @@
<!-- ******************************************************************* -->
<!-- Modal Templates START -->
<!-- ******************************************************************* -->
<!-- Jail Config Modal -->
<!-- ========================= Jail Config Modal ========================= -->
<div id="jailConfigModal" class="hidden fixed inset-0 z-50 overflow-y-auto" style="z-index: 60;">
<div class="relative flex min-h-full w-full items-center justify-center p-2 sm:p-4">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all my-4 sm:my-8" style="max-width: 90vw; max-height: calc(100vh - 2rem); display: flex; flex-direction: column;">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4" style="flex: 1; overflow-y: auto; min-height: 0;">
<div class="sm:flex sm:items-start">
@@ -1008,7 +945,6 @@
</button>
</div>
<div class="mt-4 space-y-4">
<!-- Filter Configuration -->
<div>
<div class="flex items-center justify-between mb-2">
<label class="block text-sm font-medium text-gray-700" data-i18n="modal.filter_config_label">Filter Configuration</label>
@@ -1037,11 +973,7 @@
onfocus="preventExtensionInterference(this);"></textarea>
</div>
</div>
<!-- Divider -->
<div class="border-t border-gray-300"></div>
<!-- Jail Configuration -->
<div>
<div class="flex items-center justify-between mb-2">
<label class="block text-sm font-medium text-gray-700" data-i18n="modal.jail_config_label">Jail Configuration</label>
@@ -1070,8 +1002,6 @@
onfocus="preventExtensionInterference(this);"></textarea>
</div>
</div>
<!-- Test Logpath Button (only shown if logpath is set) -->
<div id="testLogpathSection" class="hidden">
<div id="localServerLogpathHint" class="mb-2 p-2 bg-blue-50 border border-blue-200 rounded-md text-xs text-blue-800 hidden">
<strong data-i18n="modal.local_server_logpath_note"> Note:</strong> <span data-i18n="modal.local_server_logpath_text_prefix">For a local fail2ban server (e.g. installed on container host system or in a container on same host), log files must also be mounted to the fail2ban-ui container (e.g.,</span> <code class="font-mono">-v /var/log:/var/log:ro</code> <span data-i18n="modal.local_server_logpath_text_suffix">) this is required so that the fail2ban-ui can verify logpath variables or paths when updating jails.</span>
@@ -1094,7 +1024,7 @@
</div>
</div>
<!-- Manage Jails Modal -->
<!-- ========================= Manage Jails Modal ======================== -->
<div id="manageJailsModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1122,7 +1052,6 @@
<span data-i18n="modal.create_jail">Create New Jail</span>
</button>
</div>
<!-- Dynamically filled list of jails with toggle switches -->
<div id="jailsList" class="divide-y divide-gray-200"></div>
</div>
</div>
@@ -1135,7 +1064,7 @@
</div>
</div>
<!-- Create Jail Modal -->
<!-- ========================= Create Jail Modal ========================= -->
<div id="createJailModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1182,7 +1111,7 @@
</div>
</div>
<!-- Create Filter Modal -->
<!-- ========================= Create Filter Modal ======================= -->
<div id="createFilterModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1222,7 +1151,7 @@
</div>
</div>
<!-- Server Manager Modal -->
<!-- ========================= Server Manager Modal ====================== -->
<div id="serverManagerModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1345,7 +1274,7 @@
</div>
</div>
<!-- Whois Modal -->
<!-- ========================= Whois Modal =============================== -->
<div id="whoisModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1377,7 +1306,7 @@
</div>
</div>
<!-- Advanced Actions Test Modal -->
<!-- ========================= Advanced Actions Test Modal =============== -->
<div id="advancedTestModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1411,7 +1340,7 @@
</div>
</div>
<!-- Logs Modal -->
<!-- ========================= Logs Modal ================================ -->
<div id="logsModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1446,7 +1375,7 @@
</div>
</div>
<!-- Ban Insights Modal -->
<!-- ========================= Ban Insights Modal ======================== -->
<div id="banInsightsModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1464,13 +1393,8 @@
</button>
</div>
<p class="text-sm text-gray-600 mb-4" data-i18n="logs.modal.insights_description">Country distribution and recurring offenders.</p>
<!-- Summary Cards -->
<div id="insightsSummary" class="grid gap-4 sm:grid-cols-3 mb-6"></div>
<!-- Main Content Grid -->
<div class="grid gap-6 lg:grid-cols-2">
<!-- Country Statistics -->
<div class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex items-center justify-between mb-4">
<div>
@@ -1481,8 +1405,6 @@
</div>
<div id="countryStatsContainer" class="space-y-4 max-h-96 overflow-y-auto"></div>
</div>
<!-- Recurring IPs -->
<div class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex items-center justify-between mb-4">
<div>
@@ -1504,7 +1426,7 @@
</div>
</div>
<!-- Chain Help Modal -->
<!-- ========================= Chain Help Modal ========================== -->
<div id="chainHelpModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="relative flex min-h-full w-full items-center justify-center p-4 sm:p-6">
<div class="fixed inset-0 bg-gray-500 opacity-75" aria-hidden="true"></div>
@@ -1547,12 +1469,11 @@
<!-- ********************** Modal Templates END ************************ -->
<!-- jQuery (used by Select2) -->
<!-- ******************************************************************* -->
<!-- Script Includes -->
<!-- ******************************************************************* -->
<script src="/static/vendor/jquery/jquery-3.6.0.min.js?v={{.version}}"></script>
<!-- Select2 JS -->
<script src="/static/vendor/select2/select2.min.js?v={{.version}}"></script>
<!-- Fail2ban UI JavaScript Modules -->
<script src="/static/js/globals.js?v={{.version}}"></script>
<script src="/static/js/core.js?v={{.version}}"></script>
<script src="/static/js/api.js?v={{.version}}"></script>