mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
1161 lines
78 KiB
HTML
1161 lines
78 KiB
HTML
<!--
|
|
Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
|
|
|
Copyright (C) 2025 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.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
-->
|
|
<!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 data-i18n="page.title">Fail2ban UI Dashboard</title>
|
|
<!-- Prism.js for syntax highlighting -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" />
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
<!-- Tailwind CSS - Try local first, fallback to CDN for development -->
|
|
<link rel="stylesheet" href="/static/tailwind.css" onerror="
|
|
console.warn('Local Tailwind CSS not found, using CDN. For production, build Tailwind CSS. See README.md for instructions.');
|
|
var script = document.createElement('script');
|
|
script.src = 'https://cdn.tailwindcss.com';
|
|
document.head.appendChild(script);
|
|
this.onerror = null;
|
|
">
|
|
<!-- Font Awesome for icons -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<!-- Select2 CSS -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" />
|
|
<!-- Fail2ban UI CSS -->
|
|
<link rel="stylesheet" href="/static/fail2ban-ui.css">
|
|
<!-- LOTR Theme CSS (loaded conditionally) -->
|
|
<link rel="stylesheet" href="/static/lotr.css" id="lotr-css" disabled>
|
|
<!-- Google Fonts for LOTR theme -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=MedievalSharp&display=swap" rel="stylesheet">
|
|
</head>
|
|
|
|
<body class="bg-gray-50 overflow-y-scroll">
|
|
|
|
<!-- Loading Overlay -->
|
|
<div id="loading-overlay" class="fixed inset-0 flex items-center justify-center z-50 bg-black bg-opacity-50 backdrop-blur-sm">
|
|
<div class="h-12 w-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
</div>
|
|
|
|
<!-- Restart Banner -->
|
|
<div id="restartBanner" class="bg-yellow-400 text-gray-900 p-3 text-center">
|
|
<div class="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-center gap-4">
|
|
<strong data-i18n="restart_banner.message">Fail2ban configuration changed! To apply the changes, please: </strong>
|
|
<button class="bg-gray-800 text-white px-4 py-2 rounded hover:bg-gray-700 transition-colors" onclick="restartFail2ban()" data-i18n="restart_banner.button">Restart Service</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast-container"></div>
|
|
|
|
<!-- ******************************************************************* -->
|
|
<!-- Navigation START -->
|
|
<!-- ******************************************************************* -->
|
|
<nav class="bg-blue-600 text-white shadow-lg">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex items-center justify-between h-16">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<span class="text-xl font-bold">Fail2ban UI</span>
|
|
</div>
|
|
</div>
|
|
<div class="hidden md:block">
|
|
<div class="ml-10 flex items-baseline space-x-4">
|
|
<a href="#" onclick="showSection('dashboardSection')" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" data-i18n="nav.dashboard">Dashboard</a>
|
|
<a href="#" onclick="showSection('filterSection')" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" data-i18n="nav.filter_debug">Filter Debug</a>
|
|
<a href="#" onclick="showSection('settingsSection')" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" data-i18n="nav.settings">Settings</a>
|
|
</div>
|
|
</div>
|
|
<div class="md:hidden">
|
|
<button type="button" class="inline-flex items-center justify-center p-2 rounded-md text-white hover:text-white hover:bg-blue-700 focus:outline-none" onclick="toggleMobileMenu()">
|
|
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Mobile menu -->
|
|
<div id="mobileMenu" class="hidden md:hidden">
|
|
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<!-- ************************ Navigation END *************************** -->
|
|
|
|
|
|
<!-- Main Content -->
|
|
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
|
<!-- ******************************************************************* -->
|
|
<!-- Dashboard Page START -->
|
|
<!-- ******************************************************************* -->
|
|
<div id="dashboardSection">
|
|
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800" data-i18n="dashboard.title">Dashboard</h1>
|
|
<p id="currentServerSubtitle" class="text-sm text-gray-500 mt-1"></p>
|
|
</div>
|
|
<div class="flex flex-col sm:flex-row sm:items-center gap-3">
|
|
<div id="serverSelectorContainer" class="min-w-[220px]"></div>
|
|
<div class="text-sm text-gray-500 sm:ml-2">
|
|
<span data-i18n="dashboard.external_ip">Your ext. IP:</span>
|
|
<span id="external-ip" class="font-medium text-blue-600 hover:underline cursor-pointer">Loading…</span>
|
|
</div>
|
|
<div class="flex flex-col sm:flex-row gap-3">
|
|
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors flex items-center gap-2"
|
|
onclick="openServerManager()">
|
|
<i class="fas fa-network-wired"></i>
|
|
<span data-i18n="dashboard.manage_servers">Manage Servers</span>
|
|
</button>
|
|
<button class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors flex items-center gap-2"
|
|
onclick="openManageJailsModal()">
|
|
<i class="fas fa-cog"></i>
|
|
<span data-i18n="dashboard.manage_jails">Manage Jails</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="dashboard"></div> <!-- Dynamic content from the API -->
|
|
</div>
|
|
<!-- ********************** Dashboard Page END ************************* -->
|
|
|
|
|
|
<!-- ******************************************************************* -->
|
|
<!-- Filter-Debug Page 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>
|
|
|
|
<div id="filterNotice" class="hidden mb-4 text-sm text-yellow-700 bg-yellow-100 border border-yellow-200 rounded px-4 py-3"></div>
|
|
|
|
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
|
<!-- Dropdown of available jail/filters -->
|
|
<div class="mb-4">
|
|
<label for="filterSelect" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="filter_debug.select_filter">Select a Filter</label>
|
|
<select id="filterSelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
|
|
</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"
|
|
data-i18n-placeholder="filter_debug.log_lines_placeholder" placeholder="Enter log lines here..."></textarea>
|
|
</div>
|
|
|
|
<button class="bg-gray-600 text-white px-4 py-2 rounded hover:bg-gray-700 transition-colors" onclick="testSelectedFilter()" data-i18n="filter_debug.test_filter">Test Filter</button>
|
|
</div>
|
|
|
|
<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 *********************** -->
|
|
|
|
|
|
<!-- ******************************************************************* -->
|
|
<!-- 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 -->
|
|
<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">
|
|
<option value="en">English</option>
|
|
<option value="de">Deutsch</option>
|
|
<option value="es">Español</option>
|
|
<option value="fr">Français</option>
|
|
<option value="it">Italiano</option>
|
|
<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"
|
|
data-i18n-placeholder="settings.server_port_placeholder" placeholder="e.g., 8080" required min="80" max="65535" />
|
|
<p class="mt-1 text-sm text-gray-500" id="portEnvHint" style="display: none;">
|
|
<span data-i18n="settings.port_env_set">Port is set via PORT environment variable:</span>
|
|
<span id="portEnvValue"></span>. <span data-i18n="settings.port_env_hint">To change the port via Web UI, remove the PORT environment variable and restart the container.</span>
|
|
</p>
|
|
<p class="text-xs text-gray-500 mt-1" id="portRestartHint" style="display: none;" data-i18n="settings.port_restart_hint">⚠️ Port changes require a container restart to take effect.</p>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="callbackURL" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.callback_url">Fail2ban Callback URL</label>
|
|
<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="callbackURL"
|
|
data-i18n-placeholder="settings.callback_url_placeholder" placeholder="http://127.0.0.1:8080" />
|
|
<p class="text-xs text-gray-500 mt-1" 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>
|
|
|
|
<!-- Debug Log Output -->
|
|
<div class="flex items-center border border-gray-200 rounded-lg p-2 overflow-x-auto bg-gray-50">
|
|
<input type="checkbox" id="debugMode" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out">
|
|
<label for="debugMode" class="ml-2 block text-sm text-gray-700" data-i18n="settings.enable_debug">Enable Debug Log</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
<h3 class="text-lg font-medium text-gray-900 mb-2" data-i18n="settings.advanced.title">Advanced Actions for Recurring Offenders</h3>
|
|
<p class="text-sm text-gray-500" data-i18n="settings.advanced.description">Automatically add recurring offenders to an external firewall once they hit a specific threshold.</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button type="button" class="px-3 py-2 text-sm rounded border border-gray-300 text-gray-700 hover:bg-gray-50" onclick="refreshPermanentBlockLog()" data-i18n="settings.advanced.refresh_log">Refresh Log</button>
|
|
<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">
|
|
<option value="" data-i18n="settings.advanced.integration_none">Select integration</option>
|
|
<option value="mikrotik">Mikrotik</option>
|
|
<option value="pfsense">pfSense</option>
|
|
</select>
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.advanced.integration_hint">Choose where permanent bans should be synchronized.</p>
|
|
</div>
|
|
|
|
<div id="advancedMikrotikFields" class="hidden border border-gray-200 rounded-lg p-4 overflow-x-auto bg-gray-50">
|
|
<p class="text-sm text-gray-500" data-i18n="settings.advanced.mikrotik.note">Provide SSH credentials and the address list where IPs should be added.</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="mikrotikHost" data-i18n="settings.advanced.mikrotik.host">Host</label>
|
|
<input id="mikrotikHost" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="mikrotikPort" data-i18n="settings.advanced.mikrotik.port">Port</label>
|
|
<input id="mikrotikPort" type="number" min="1" max="65535" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500" placeholder="22">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="mikrotikUsername" data-i18n="settings.advanced.mikrotik.username">SSH Username</label>
|
|
<input id="mikrotikUsername" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="mikrotikPassword" data-i18n="settings.advanced.mikrotik.password">SSH Password</label>
|
|
<input id="mikrotikPassword" type="password" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="mikrotikSSHKey" data-i18n="settings.advanced.mikrotik.key">SSH Key Path (optional)</label>
|
|
<input id="mikrotikSSHKey" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="mikrotikList" data-i18n="settings.advanced.mikrotik.list">Address List Name</label>
|
|
<input id="mikrotikList" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500" placeholder="fail2ban-permanent">
|
|
</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" data-i18n="settings.advanced.pfsense.note">Requires the pfSense API package. Enter the API credentials and alias to manage.</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div class="md:col-span-2">
|
|
<label class="block text-sm font-medium text-gray-700" for="pfSenseBaseURL" data-i18n="settings.advanced.pfsense.base_url">Base URL</label>
|
|
<input id="pfSenseBaseURL" type="url" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500" placeholder="https://firewall.local">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="pfSenseToken" data-i18n="settings.advanced.pfsense.token">API Token</label>
|
|
<input id="pfSenseToken" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="pfSenseSecret" data-i18n="settings.advanced.pfsense.secret">API Secret</label>
|
|
<input id="pfSenseSecret" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="pfSenseAlias" data-i18n="settings.advanced.pfsense.alias">Alias Name</label>
|
|
<input id="pfSenseAlias" type="text" class="mt-1 w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="pfSenseSkipTLS" class="h-4 w-4 text-blue-600 border-gray-300 rounded">
|
|
<label for="pfSenseSkipTLS" class="ml-2 text-sm text-gray-700" data-i18n="settings.advanced.pfsense.skip_tls">Skip TLS verification (self-signed)</label>
|
|
</div>
|
|
</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">
|
|
<p class="text-sm text-gray-500 p-4" data-i18n="settings.advanced.log_empty">No permanent blocks recorded yet.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Settings Group -->
|
|
<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>
|
|
<div class="mb-4">
|
|
<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" 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>
|
|
<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">
|
|
Choose the countries for which you want to receive email alerts when a block is triggered.
|
|
</p>
|
|
<select id="alertCountries" class="w-full border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" multiple>
|
|
<option value="ALL">ALL (Every Country)</option>
|
|
<option value="AF">Afghanistan (AF)</option>
|
|
<option value="AL">Albania (AL)</option>
|
|
<option value="DZ">Algeria (DZ)</option>
|
|
<option value="AD">Andorra (AD)</option>
|
|
<option value="AO">Angola (AO)</option>
|
|
<option value="AG">Antigua and Barbuda (AG)</option>
|
|
<option value="AR">Argentina (AR)</option>
|
|
<option value="AM">Armenia (AM)</option>
|
|
<option value="AU">Australia (AU)</option>
|
|
<option value="AT">Austria (AT)</option>
|
|
<option value="AZ">Azerbaijan (AZ)</option>
|
|
<option value="BS">Bahamas (BS)</option>
|
|
<option value="BH">Bahrain (BH)</option>
|
|
<option value="BD">Bangladesh (BD)</option>
|
|
<option value="BB">Barbados (BB)</option>
|
|
<option value="BY">Belarus (BY)</option>
|
|
<option value="BE">Belgium (BE)</option>
|
|
<option value="BZ">Belize (BZ)</option>
|
|
<option value="BJ">Benin (BJ)</option>
|
|
<option value="BT">Bhutan (BT)</option>
|
|
<option value="BO">Bolivia (BO)</option>
|
|
<option value="BA">Bosnia and Herzegovina (BA)</option>
|
|
<option value="BW">Botswana (BW)</option>
|
|
<option value="BR">Brazil (BR)</option>
|
|
<option value="BN">Brunei (BN)</option>
|
|
<option value="BG">Bulgaria (BG)</option>
|
|
<option value="BF">Burkina Faso (BF)</option>
|
|
<option value="BI">Burundi (BI)</option>
|
|
<option value="CV">Cabo Verde (CV)</option>
|
|
<option value="KH">Cambodia (KH)</option>
|
|
<option value="CM">Cameroon (CM)</option>
|
|
<option value="CA">Canada (CA)</option>
|
|
<option value="CF">Central African Republic (CF)</option>
|
|
<option value="TD">Chad (TD)</option>
|
|
<option value="CL">Chile (CL)</option>
|
|
<option value="CN">China (CN)</option>
|
|
<option value="CO">Colombia (CO)</option>
|
|
<option value="KM">Comoros (KM)</option>
|
|
<option value="CG">Congo - Brazzaville (CG)</option>
|
|
<option value="CD">Congo - Kinshasa (CD)</option>
|
|
<option value="CR">Costa Rica (CR)</option>
|
|
<option value="CI">Côte d'Ivoire (CI)</option>
|
|
<option value="HR">Croatia (HR)</option>
|
|
<option value="CU">Cuba (CU)</option>
|
|
<option value="CY">Cyprus (CY)</option>
|
|
<option value="CZ">Czechia (CZ)</option>
|
|
<option value="DK">Denmark (DK)</option>
|
|
<option value="DJ">Djibouti (DJ)</option>
|
|
<option value="DM">Dominica (DM)</option>
|
|
<option value="DO">Dominican Republic (DO)</option>
|
|
<option value="EC">Ecuador (EC)</option>
|
|
<option value="EG">Egypt (EG)</option>
|
|
<option value="SV">El Salvador (SV)</option>
|
|
<option value="GQ">Equatorial Guinea (GQ)</option>
|
|
<option value="ER">Eritrea (ER)</option>
|
|
<option value="EE">Estonia (EE)</option>
|
|
<option value="SZ">Eswatini (SZ)</option>
|
|
<option value="ET">Ethiopia (ET)</option>
|
|
<option value="FJ">Fiji (FJ)</option>
|
|
<option value="FI">Finland (FI)</option>
|
|
<option value="FR">France (FR)</option>
|
|
<option value="GA">Gabon (GA)</option>
|
|
<option value="GM">Gambia (GM)</option>
|
|
<option value="GE">Georgia (GE)</option>
|
|
<option value="DE">Germany (DE)</option>
|
|
<option value="GH">Ghana (GH)</option>
|
|
<option value="GR">Greece (GR)</option>
|
|
<option value="GD">Grenada (GD)</option>
|
|
<option value="GT">Guatemala (GT)</option>
|
|
<option value="GN">Guinea (GN)</option>
|
|
<option value="GW">Guinea-Bissau (GW)</option>
|
|
<option value="GY">Guyana (GY)</option>
|
|
<option value="HT">Haiti (HT)</option>
|
|
<option value="HN">Honduras (HN)</option>
|
|
<option value="HU">Hungary (HU)</option>
|
|
<option value="IS">Iceland (IS)</option>
|
|
<option value="IN">India (IN)</option>
|
|
<option value="ID">Indonesia (ID)</option>
|
|
<option value="IR">Iran (IR)</option>
|
|
<option value="IQ">Iraq (IQ)</option>
|
|
<option value="IE">Ireland (IE)</option>
|
|
<option value="IL">Israel (IL)</option>
|
|
<option value="IT">Italy (IT)</option>
|
|
<option value="JM">Jamaica (JM)</option>
|
|
<option value="JP">Japan (JP)</option>
|
|
<option value="JO">Jordan (JO)</option>
|
|
<option value="KZ">Kazakhstan (KZ)</option>
|
|
<option value="KE">Kenya (KE)</option>
|
|
<option value="KI">Kiribati (KI)</option>
|
|
<option value="KP">North Korea (KP)</option>
|
|
<option value="KR">South Korea (KR)</option>
|
|
<option value="KW">Kuwait (KW)</option>
|
|
<option value="KG">Kyrgyzstan (KG)</option>
|
|
<option value="LA">Laos (LA)</option>
|
|
<option value="LV">Latvia (LV)</option>
|
|
<option value="LB">Lebanon (LB)</option>
|
|
<option value="LS">Lesotho (LS)</option>
|
|
<option value="LR">Liberia (LR)</option>
|
|
<option value="LY">Libya (LY)</option>
|
|
<option value="LI">Liechtenstein (LI)</option>
|
|
<option value="LT">Lithuania (LT)</option>
|
|
<option value="LU">Luxembourg (LU)</option>
|
|
<option value="MG">Madagascar (MG)</option>
|
|
<option value="MW">Malawi (MW)</option>
|
|
<option value="MY">Malaysia (MY)</option>
|
|
<option value="MV">Maldives (MV)</option>
|
|
<option value="ML">Mali (ML)</option>
|
|
<option value="MT">Malta (MT)</option>
|
|
<option value="MH">Marshall Islands (MH)</option>
|
|
<option value="MR">Mauritania (MR)</option>
|
|
<option value="MU">Mauritius (MU)</option>
|
|
<option value="MX">Mexico (MX)</option>
|
|
<option value="FM">Micronesia (FM)</option>
|
|
<option value="LOTR">Middle-earth (LOTR)</option>
|
|
<option value="MD">Moldova (MD)</option>
|
|
<option value="MC">Monaco (MC)</option>
|
|
<option value="MN">Mongolia (MN)</option>
|
|
<option value="ME">Montenegro (ME)</option>
|
|
<option value="MA">Morocco (MA)</option>
|
|
<option value="MZ">Mozambique (MZ)</option>
|
|
<option value="MM">Myanmar (MM)</option>
|
|
<option value="NA">Namibia (NA)</option>
|
|
<option value="NR">Nauru (NR)</option>
|
|
<option value="NP">Nepal (NP)</option>
|
|
<option value="NL">Netherlands (NL)</option>
|
|
<option value="NZ">New Zealand (NZ)</option>
|
|
<option value="NI">Nicaragua (NI)</option>
|
|
<option value="NE">Niger (NE)</option>
|
|
<option value="NG">Nigeria (NG)</option>
|
|
<option value="NO">Norway (NO)</option>
|
|
<option value="OM">Oman (OM)</option>
|
|
<option value="PK">Pakistan (PK)</option>
|
|
<option value="PW">Palau (PW)</option>
|
|
<option value="PS">Palestine (PS)</option>
|
|
<option value="PA">Panama (PA)</option>
|
|
<option value="PG">Papua New Guinea (PG)</option>
|
|
<option value="PY">Paraguay (PY)</option>
|
|
<option value="PE">Peru (PE)</option>
|
|
<option value="PH">Philippines (PH)</option>
|
|
<option value="PL">Polland (PL)</option>
|
|
<option value="PT">Portugal (PT)</option>
|
|
<option value="QA">Qatar (QA)</option>
|
|
<option value="RO">Romania (RO)</option>
|
|
<option value="RU">Russia (RU)</option>
|
|
<option value="RW">Rwanda (RW)</option>
|
|
<option value="KN">Saint Kitts and Nevis (KN)</option>
|
|
<option value="LC">Saint Lucia (LC)</option>
|
|
<option value="VC">Saint Vincent and the Grenadines (VC)</option>
|
|
<option value="WS">Samoa (WS)</option>
|
|
<option value="SM">San Marino (SM)</option>
|
|
<option value="ST">Sao Tome and Principe (ST)</option>
|
|
<option value="SA">Saudi Arabia (SA)</option>
|
|
<option value="SN">Senegal (SN)</option>
|
|
<option value="RS">Serbia (RS)</option>
|
|
<option value="SC">Seychelles (SC)</option>
|
|
<option value="SL">Sierra Leone (SL)</option>
|
|
<option value="SG">Singapore (SG)</option>
|
|
<option value="SK">Slovakia (SK)</option>
|
|
<option value="SI">Slovenia (SI)</option>
|
|
<option value="SB">Solomon Islands (SB)</option>
|
|
<option value="SO">Somalia (SO)</option>
|
|
<option value="ZA">South Africa (ZA)</option>
|
|
<option value="SS">South Sudan (SS)</option>
|
|
<option value="ES">Spain (ES)</option>
|
|
<option value="LK">Sri Lanka (LK)</option>
|
|
<option value="SD">Sudan (SD)</option>
|
|
<option value="SR">Suriname (SR)</option>
|
|
<option value="SE">Sweden (SE)</option>
|
|
<option value="CH">Switzerland (CH)</option>
|
|
<option value="SY">Syria (SY)</option>
|
|
<option value="TW">Taiwan (TW)</option>
|
|
<option value="TJ">Tajikistan (TJ)</option>
|
|
<option value="TZ">Tanzania (TZ)</option>
|
|
<option value="TH">Thailand (TH)</option>
|
|
<option value="TL">Timor-Leste (TL)</option>
|
|
<option value="TG">Togo (TG)</option>
|
|
<option value="TO">Tonga (TO)</option>
|
|
<option value="TT">Trinidad and Tobago (TT)</option>
|
|
<option value="TN">Tunisia (TN)</option>
|
|
<option value="TR">Turkey (TR)</option>
|
|
<option value="TM">Turkmenistan (TM)</option>
|
|
<option value="TV">Tuvalu (TV)</option>
|
|
<option value="UG">Uganda (UG)</option>
|
|
<option value="UA">Ukraine (UA)</option>
|
|
<option value="AE">United Arab Emirates (AE)</option>
|
|
<option value="GB">United Kingdom (GB)</option>
|
|
<option value="US">United States (US)</option>
|
|
<option value="UY">Uruguay (UY)</option>
|
|
<option value="UZ">Uzbekistan (UZ)</option>
|
|
<option value="VU">Vanuatu (VU)</option>
|
|
<option value="VE">Venezuela (VE)</option>
|
|
<option value="VN">Vietnam (VN)</option>
|
|
<option value="YE">Yemen (YE)</option>
|
|
<option value="ZM">Zambia (ZM)</option>
|
|
<option value="ZW">Zimbabwe (ZW)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SMTP Configuration Group -->
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4" data-i18n="settings.smtp">SMTP Configuration</h3>
|
|
<div class="mb-4">
|
|
<label for="smtpHost" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_host">SMTP Host</label>
|
|
<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="smtpHost"
|
|
data-i18n-placeholder="settings.smtp_host_placeholder" placeholder="e.g., smtp.gmail.com" required />
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="smtpPort" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_port">SMTP Port</label>
|
|
<select id="smtpPort" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="587" selected>587 (Recommended - STARTTLS)</option>
|
|
<option value="465" disabled>465 (Not Supported)</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="smtpUsername" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_username">SMTP Username</label>
|
|
<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="smtpUsername"
|
|
data-i18n-placeholder="settings.smtp_username_placeholder" placeholder="e.g., user@example.com" required />
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="smtpPassword" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_password">SMTP Password</label>
|
|
<input type="password" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" id="smtpPassword"
|
|
data-i18n-placeholder="settings.smtp_password_placeholder" placeholder="Enter SMTP Password" required />
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="smtpFrom" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_sender">Sender Email</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" id="smtpFrom"
|
|
data-i18n-placeholder="settings.smtp_sender_placeholder" placeholder="noreply@swissmakers.ch" required />
|
|
</div>
|
|
<div class="flex items-center mb-4">
|
|
<input type="checkbox" id="smtpUseTLS" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out">
|
|
<label for="smtpUseTLS" class="ml-2 block text-sm text-gray-700" data-i18n="settings.smtp_tls">Use TLS (Recommended)</label>
|
|
</div>
|
|
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded hover:bg-gray-700 transition-colors" onclick="sendTestEmail()" data-i18n="settings.send_test_email">Send Test Email</button>
|
|
</div>
|
|
|
|
<!-- Fail2Ban Configuration Group -->
|
|
<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" />
|
|
<label for="bantimeIncrement" class="ml-2 block text-sm font-medium text-gray-700" data-i18n="settings.enable_bantime_increment">Enable Bantime Increment</label>
|
|
</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>
|
|
|
|
<!-- 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>
|
|
<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="banTime"
|
|
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>
|
|
|
|
<!-- 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. iptables-multiport, iptables-allports, firewallcmd-multiport, etc). It is used to define action_* variables.</p>
|
|
<select id="banaction" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="iptables-multiport">iptables-multiport</option>
|
|
<option value="iptables-allports">iptables-allports</option>
|
|
<option value="iptables-new">iptables-new</option>
|
|
<option value="iptables-ipset">iptables-ipset</option>
|
|
<option value="iptables-ipset-proto4">iptables-ipset-proto4</option>
|
|
<option value="iptables-ipset-proto6">iptables-ipset-proto6</option>
|
|
<option value="iptables-ipset-proto6-allports">iptables-ipset-proto6-allports</option>
|
|
<option value="iptables-multiport-log">iptables-multiport-log</option>
|
|
<option value="iptables-xt_recent-echo">iptables-xt_recent-echo</option>
|
|
<option value="firewallcmd-multiport">firewallcmd-multiport</option>
|
|
<option value="firewallcmd-allports">firewallcmd-allports</option>
|
|
<option value="firewallcmd-ipset">firewallcmd-ipset</option>
|
|
<option value="firewallcmd-new">firewallcmd-new</option>
|
|
<option value="firewallcmd-rich-rules">firewallcmd-rich-rules</option>
|
|
<option value="firewallcmd-rich-logging">firewallcmd-rich-logging</option>
|
|
<option value="nftables-multiport">nftables-multiport</option>
|
|
<option value="nftables-allports">nftables-allports</option>
|
|
<option value="nftables">nftables</option>
|
|
<option value="shorewall">shorewall</option>
|
|
<option value="shorewall-ipset-proto6">shorewall-ipset-proto6</option>
|
|
<option value="ufw">ufw</option>
|
|
<option value="pf">pf</option>
|
|
<option value="bsd-ipfw">bsd-ipfw</option>
|
|
<option value="ipfw">ipfw</option>
|
|
<option value="ipfilter">ipfilter</option>
|
|
<option value="npf">npf</option>
|
|
<option value="osx-ipfw">osx-ipfw</option>
|
|
<option value="osx-afctl">osx-afctl</option>
|
|
<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>
|
|
<select id="banactionAllports" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="iptables-allports">iptables-allports</option>
|
|
<option value="iptables-multiport">iptables-multiport</option>
|
|
<option value="iptables-new">iptables-new</option>
|
|
<option value="iptables-ipset">iptables-ipset</option>
|
|
<option value="iptables-ipset-proto4">iptables-ipset-proto4</option>
|
|
<option value="iptables-ipset-proto6">iptables-ipset-proto6</option>
|
|
<option value="iptables-ipset-proto6-allports">iptables-ipset-proto6-allports</option>
|
|
<option value="iptables-multiport-log">iptables-multiport-log</option>
|
|
<option value="iptables-xt_recent-echo">iptables-xt_recent-echo</option>
|
|
<option value="firewallcmd-allports">firewallcmd-allports</option>
|
|
<option value="firewallcmd-multiport">firewallcmd-multiport</option>
|
|
<option value="firewallcmd-ipset">firewallcmd-ipset</option>
|
|
<option value="firewallcmd-new">firewallcmd-new</option>
|
|
<option value="firewallcmd-rich-rules">firewallcmd-rich-rules</option>
|
|
<option value="firewallcmd-rich-logging">firewallcmd-rich-logging</option>
|
|
<option value="nftables-allports">nftables-allports</option>
|
|
<option value="nftables-multiport">nftables-multiport</option>
|
|
<option value="nftables">nftables</option>
|
|
<option value="shorewall">shorewall</option>
|
|
<option value="shorewall-ipset-proto6">shorewall-ipset-proto6</option>
|
|
<option value="ufw">ufw</option>
|
|
<option value="pf">pf</option>
|
|
<option value="bsd-ipfw">bsd-ipfw</option>
|
|
<option value="ipfw">ipfw</option>
|
|
<option value="ipfilter">ipfilter</option>
|
|
<option value="npf">npf</option>
|
|
<option value="osx-ipfw">osx-ipfw</option>
|
|
<option value="osx-afctl">osx-afctl</option>
|
|
<option value="apf">apf</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>
|
|
<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="findTime"
|
|
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>
|
|
<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="maxRetry"
|
|
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>
|
|
<div class="border border-gray-300 rounded-md p-2 min-h-[60px] bg-gray-50" id="ignoreIPsContainer">
|
|
<div id="ignoreIPsTags" class="flex flex-wrap gap-2 mb-2"></div>
|
|
<input type="text" id="ignoreIPInput" class="w-full border-0 bg-transparent focus:outline-none focus:ring-0 text-sm"
|
|
data-i18n-placeholder="settings.ignore_ips_placeholder" placeholder="Enter IP address and press Enter" />
|
|
<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 ************************* -->
|
|
</main>
|
|
|
|
<!-- Footer -->
|
|
<footer class="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">
|
|
© <a href="https://swissmakers.ch" target="_blank" class="text-blue-600 hover:text-blue-800">Swissmakers GmbH</a>
|
|
-
|
|
<a href="https://github.com/swissmakers/fail2ban-ui" target="_blank" class="text-blue-600 hover:text-blue-800">GitHub</a>
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
|
|
|
|
<!-- ******************************************************************* -->
|
|
<!-- Modal Templates START -->
|
|
<!-- ******************************************************************* -->
|
|
<!-- 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">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
<span data-i18n="modal.filter_config">Filter Config:</span> <span id="modalJailName"></span>
|
|
</h3>
|
|
<button type="button" onclick="closeModal('jailConfigModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4 space-y-4">
|
|
<!-- Filter Configuration -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.filter_config_label">Filter Configuration</label>
|
|
<div class="relative" style="position: relative;">
|
|
<textarea id="filterConfigTextarea"
|
|
class="w-full border border-gray-700 rounded-md px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 h-96 font-mono text-sm bg-gray-900 text-white resize-none overflow-auto"
|
|
spellcheck="false"
|
|
autocomplete="off"
|
|
autocorrect="off"
|
|
autocapitalize="off"
|
|
data-lpignore="true"
|
|
data-1p-ignore="true"
|
|
data-bwignore="true"
|
|
data-form-type="other"
|
|
data-extension-ignore="true"
|
|
data-icloud-keychain-ignore="true"
|
|
data-safari-autofill="false"
|
|
role="textbox"
|
|
aria-label="Filter configuration editor"
|
|
name="filter-config-editor"
|
|
inputmode="text"
|
|
style="caret-color: #ffffff; line-height: 1.5; tab-size: 2; width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box; -webkit-appearance: none; appearance: none; position: relative; z-index: 2;"
|
|
wrap="off"
|
|
onfocus="preventExtensionInterference(this);"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Divider -->
|
|
<div class="border-t border-gray-300"></div>
|
|
|
|
<!-- Jail Configuration -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.jail_config_label">Jail Configuration</label>
|
|
<div class="relative" style="position: relative;">
|
|
<textarea id="jailConfigTextarea"
|
|
class="w-full border border-gray-700 rounded-md px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm bg-gray-900 text-white resize-none overflow-auto"
|
|
spellcheck="false"
|
|
autocomplete="off"
|
|
autocorrect="off"
|
|
autocapitalize="off"
|
|
data-lpignore="true"
|
|
data-1p-ignore="true"
|
|
data-bwignore="true"
|
|
data-form-type="other"
|
|
data-extension-ignore="true"
|
|
data-icloud-keychain-ignore="true"
|
|
data-safari-autofill="false"
|
|
role="textbox"
|
|
aria-label="Jail configuration editor"
|
|
name="jail-config-editor"
|
|
inputmode="text"
|
|
style="height: 300px; caret-color: #ffffff; line-height: 1.5; tab-size: 2; width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box; -webkit-appearance: none; appearance: none; position: relative; z-index: 2;"
|
|
wrap="off"
|
|
onfocus="preventExtensionInterference(this);"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Logpath Button (only shown if logpath is set) -->
|
|
<div id="testLogpathSection" class="hidden">
|
|
<button type="button"
|
|
class="inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
|
onclick="testLogpath()"
|
|
data-i18n="modal.test_logpath">Test Logpath</button>
|
|
<pre id="logpathResults" class="mt-2 p-3 bg-gray-100 rounded-md text-sm font-mono max-h-32 overflow-y-auto hidden"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse" style="flex-shrink: 0;">
|
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" onclick="saveJailConfig()" data-i18n="modal.save">Save</button>
|
|
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('jailConfigModal')" data-i18n="modal.cancel">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 1000px;">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900" data-i18n="modal.manage_jails_title">Manage Jails</h3>
|
|
<button type="button" onclick="closeModal('manageJailsModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4">
|
|
<!-- Dynamically filled list of jails with toggle switches -->
|
|
<div id="jailsList" class="divide-y divide-gray-200"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('manageJailsModal')" data-i18n="modal.close">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 1000px;">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-6">
|
|
<div class="flex flex-col gap-6">
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900" data-i18n="servers.modal.title">Manage Fail2ban Servers</h3>
|
|
<button type="button" onclick="closeModal('serverManagerModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="mt-1 text-sm text-gray-500" data-i18n="servers.modal.description">
|
|
Register remote Fail2ban instances and choose how the UI connects to them.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div>
|
|
<h4 class="text-md font-semibold text-gray-800 mb-3" data-i18n="servers.modal.list_title">Registered Servers</h4>
|
|
<div id="serverManagerList" class="space-y-3 max-h-96 overflow-y-auto pr-1"></div>
|
|
<div id="serverManagerListEmpty" class="text-sm text-gray-500 hidden" data-i18n="servers.modal.list_empty">
|
|
No servers configured yet. Add your first Fail2ban server using the form on the right.
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h4 class="text-md font-semibold text-gray-800" data-i18n="servers.modal.form_title">Add or Update Server</h4>
|
|
<button type="button" onclick="resetServerForm()" class="text-sm text-blue-600 hover:text-blue-800 font-medium" data-i18n="servers.form.new_server">New Server</button>
|
|
</div>
|
|
<form id="serverForm" class="space-y-4" onsubmit="submitServerForm(event)">
|
|
<div>
|
|
<label for="serverName" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.name">Display Name</label>
|
|
<input type="text" id="serverName" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required data-i18n-placeholder="servers.form.name_placeholder" placeholder="My Fail2ban server">
|
|
</div>
|
|
<div>
|
|
<label for="serverType" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.type">Connection Type</label>
|
|
<select id="serverType" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required onchange="onServerTypeChange(this.value)">
|
|
<option value="local" data-i18n="servers.type.local">Local (same host)</option>
|
|
<option value="ssh" data-i18n="servers.type.ssh">SSH</option>
|
|
<option value="agent" data-i18n="servers.type.agent">API Agent</option>
|
|
</select>
|
|
</div>
|
|
<div data-server-fields="ssh agent">
|
|
<label for="serverHost" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.host">Hostname / IP</label>
|
|
<input type="text" id="serverHost" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.host_placeholder" placeholder="fail2ban.example.com">
|
|
</div>
|
|
<div data-server-fields="ssh agent">
|
|
<label for="serverPort" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.port">Port</label>
|
|
<input type="number" id="serverPort" min="1" max="65535" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.port_placeholder" placeholder="22">
|
|
</div>
|
|
<div data-server-fields="local ssh">
|
|
<label for="serverSocket" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.socket_path">Fail2ban Socket Path</label>
|
|
<input type="text" id="serverSocket" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.socket_path_placeholder" placeholder="/var/run/fail2ban/fail2ban.sock">
|
|
</div>
|
|
<div data-server-fields="local">
|
|
<label for="serverLogPath" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.log_path">Fail2ban Log Path</label>
|
|
<input type="text" id="serverLogPath" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.log_path_placeholder" placeholder="/var/log/fail2ban.log">
|
|
</div>
|
|
<div data-server-fields="local">
|
|
<label for="serverHostname" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.hostname">Server Hostname</label>
|
|
<input type="text" id="serverHostname" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.hostname_placeholder" placeholder="optional">
|
|
</div>
|
|
<div data-server-fields="ssh">
|
|
<label for="serverSSHUser" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.ssh_user">SSH User</label>
|
|
<input type="text" id="serverSSHUser" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.ssh_user_placeholder" placeholder="root">
|
|
</div>
|
|
<div data-server-fields="ssh">
|
|
<label for="serverSSHKey" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.ssh_key">SSH Private Key Path</label>
|
|
<input type="text" id="serverSSHKey" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.ssh_key_placeholder" placeholder="~/.ssh/id_rsa">
|
|
</div>
|
|
<div data-server-fields="ssh">
|
|
<label for="serverSSHKeySelect" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.select_key">Select Private Key</label>
|
|
<select id="serverSSHKeySelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="" data-i18n="servers.form.select_key_placeholder">Manual entry</option>
|
|
</select>
|
|
</div>
|
|
<div data-server-fields="agent">
|
|
<label for="serverAgentUrl" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.agent_url">Agent URL</label>
|
|
<input type="url" id="serverAgentUrl" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.agent_url_placeholder" placeholder="https://host:9443">
|
|
</div>
|
|
<div data-server-fields="agent">
|
|
<label for="serverAgentSecret" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.agent_secret">Agent Secret</label>
|
|
<input type="text" id="serverAgentSecret" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.agent_secret_placeholder" placeholder="shared secret token">
|
|
</div>
|
|
<div>
|
|
<label for="serverTags" class="block text-sm font-medium text-gray-700 mb-1" data-i18n="servers.form.tags">Tags</label>
|
|
<input type="text" id="serverTags" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="servers.form.tags_placeholder" placeholder="comma,separated,tags">
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="serverDefault" class="h-4 w-4 text-blue-600 border-gray-300 rounded">
|
|
<label for="serverDefault" class="ml-2 text-sm text-gray-700" data-i18n="servers.form.set_default">Set as default server</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="serverEnabled" class="h-4 w-4 text-blue-600 border-gray-300 rounded">
|
|
<label for="serverEnabled" class="ml-2 text-sm text-gray-700" data-i18n="servers.form.enabled">Enable connector</label>
|
|
</div>
|
|
<input type="hidden" id="serverId">
|
|
<div class="flex gap-3">
|
|
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors" data-i18n="servers.form.submit">Save Server</button>
|
|
<button type="button" class="px-4 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors" onclick="resetServerForm()" data-i18n="servers.form.reset">Reset</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('serverManagerModal')" data-i18n="modal.close">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 800px;">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
<span data-i18n="logs.modal.whois_title">Whois Information</span> - <span id="whoisModalIP"></span>
|
|
</h3>
|
|
<button type="button" onclick="closeModal('whoisModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4">
|
|
<pre id="whoisModalContent" class="w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-900 text-white font-mono text-xs overflow-x-auto" style="max-height: 70vh; white-space: pre-wrap; word-wrap: break-word;"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('whoisModal')" data-i18n="modal.close">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 800px;">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900" data-i18n="settings.advanced.test_title">Manually Block / Test</h3>
|
|
<button type="button" onclick="closeModal('advancedTestModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4 space-y-4">
|
|
<div>
|
|
<label for="advancedTestIP" class="block text-sm font-medium text-gray-700" data-i18n="settings.advanced.test_ip">IP address</label>
|
|
<input type="text" id="advancedTestIP" 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="203.0.113.10">
|
|
</div>
|
|
<div>
|
|
<label for="advancedTestServer" class="block text-sm font-medium text-gray-700" data-i18n="settings.advanced.test_server">Optional server</label>
|
|
<select id="advancedTestServer" 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">
|
|
<option value="" data-i18n="settings.advanced.test_server_none">Use global integration settings</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-3">
|
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm" onclick="submitAdvancedTest('block')" data-i18n="settings.advanced.test_block">Block IP</button>
|
|
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('advancedTestModal')" data-i18n="modal.close">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 1200px;">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
<span data-i18n="logs.modal.logs_title">Logs</span> - <span id="logsModalIP"></span>
|
|
</h3>
|
|
<button type="button" onclick="closeModal('logsModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="text-sm text-gray-600 mb-4">
|
|
<span data-i18n="logs.modal.jail">Jail:</span> <span id="logsModalJail" class="font-semibold"></span>
|
|
</p>
|
|
<div class="mt-4">
|
|
<pre id="logsModalContent" class="w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-900 text-white font-mono text-xs overflow-x-auto" style="max-height: 70vh; white-space: pre-wrap; word-wrap: break-word;"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('logsModal')" data-i18n="modal.close">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 1200px;">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900" data-i18n="logs.modal.insights_title">Ban Insights</h3>
|
|
<button type="button" onclick="closeModal('banInsightsModal')" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600" aria-label="Close">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</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>
|
|
<h4 class="text-base font-semibold text-gray-900" data-i18n="logs.modal.insights_countries">Bans by country</h4>
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="logs.modal.insights_countries_hint">Top origins for the selected time range.</p>
|
|
</div>
|
|
<span class="inline-flex items-center rounded-full bg-blue-100 px-3 py-1 text-xs font-medium text-blue-700">Geo</span>
|
|
</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>
|
|
<h4 class="text-base font-semibold text-gray-900" data-i18n="logs.modal.insights_recurring">Recurring IPs</h4>
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="logs.modal.insights_recurring_hint">IP addresses repeatedly triggering Fail2ban.</p>
|
|
</div>
|
|
<span class="inline-flex items-center rounded-full bg-amber-100 px-3 py-1 text-xs font-medium text-amber-700">Watchlist</span>
|
|
</div>
|
|
<div id="recurringIPsContainer" class="space-y-4 max-h-96 overflow-y-auto"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('banInsightsModal')" data-i18n="modal.close">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ********************** Modal Templates END ************************ -->
|
|
|
|
<!-- jQuery (used by Select2) -->
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<!-- Select2 JS -->
|
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
|
|
|
|
<!-- Fail2ban UI JavaScript Modules -->
|
|
<script src="/static/js/globals.js"></script>
|
|
<script src="/static/js/core.js"></script>
|
|
<script src="/static/js/api.js"></script>
|
|
<script src="/static/js/utils.js"></script>
|
|
<script src="/static/js/validation.js"></script>
|
|
<script src="/static/js/modals.js"></script>
|
|
<script src="/static/js/translations.js"></script>
|
|
<script src="/static/js/ignoreips.js"></script>
|
|
<script src="/static/js/dashboard.js"></script>
|
|
<script src="/static/js/servers.js"></script>
|
|
<script src="/static/js/jails.js"></script>
|
|
<script src="/static/js/settings.js"></script>
|
|
<script src="/static/js/filters.js"></script>
|
|
<script src="/static/js/lotr.js"></script>
|
|
<script src="/static/js/init.js"></script>
|
|
</body>
|
|
</html>
|