mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
1391 lines
98 KiB
HTML
1391 lines
98 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="/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>
|
|
|
|
<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">
|
|
<a href="/" class="text-xl font-bold hover:text-blue-200 transition-colors cursor-pointer">Fail2ban UI</a>
|
|
</div>
|
|
<div id="backendStatus" class="ml-4 flex items-center gap-2">
|
|
<span class="w-2 h-2 rounded-full bg-gray-400" id="statusDot"></span>
|
|
<span id="statusText" class="text-xs">Connecting...</span>
|
|
</div>
|
|
</div>
|
|
<div class="hidden md:block">
|
|
<div class="ml-10 flex items-baseline space-x-4 items-center">
|
|
<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 id="clockDisplay" class="ml-4 text-sm font-mono">
|
|
<span id="clockTime">--:--:--</span>
|
|
</div>
|
|
</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>
|
|
{{if not .disableExternalIP}}
|
|
<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>
|
|
{{end}}
|
|
<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">
|
|
<div class="mb-4 flex justify-between items-end">
|
|
<div class="flex-1 mr-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>
|
|
<div class="flex gap-2">
|
|
<select id="filterSelect" class="flex-1 border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
|
|
<button type="button" onclick="deleteFilter()" id="deleteFilterBtn" class="px-3 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled title="Delete selected filter">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</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>
|
|
|
|
<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>
|
|
<a href="#" id="toggleCallbackSecretLink" class="text-sm text-blue-600 hover:text-blue-800 underline" onclick="toggleCallbackSecretVisibility(); return false;">show secret</a>
|
|
</div>
|
|
<input type="password" class="w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-100 cursor-not-allowed" id="callbackSecret" readonly
|
|
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">
|
|
<label for="debugMode" class="ml-2 block text-sm text-gray-700" data-i18n="settings.enable_debug">Enable Debug Log</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="consoleOutput" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out" onchange="toggleConsoleOutput(true)">
|
|
<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">
|
|
<div class="flex gap-1.5">
|
|
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
|
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
|
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<h4 class="text-sm font-medium text-gray-100 ml-2" data-i18n="settings.console.title">Console Output</h4>
|
|
</div>
|
|
<button type="button" onclick="clearConsole()" class="text-xs text-gray-100 hover:text-white px-2 py-1 rounded hover:bg-gray-700 transition-colors" data-i18n="settings.console.clear">Clear</button>
|
|
</div>
|
|
<div id="consoleOutputWindow" class="overflow-y-auto p-4 font-mono text-sm text-green-400 bg-gray-900" style="max-height: 430px; min-height: 200px;">
|
|
<div class="text-gray-500">Console output will appear here...</div>
|
|
</div>
|
|
</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>
|
|
<option value="opnsense">OPNsense</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 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">
|
|
<a href="https://github.com/jaredhendrickson13/pfsense-api/releases" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-800 underline" data-i18n="settings.advanced.pfsense.install_link">Install REST API Package</a>
|
|
<span class="text-gray-500 mx-2">•</span>
|
|
<a href="https://pfrest.org/AUTHENTICATION_AND_AUTHORIZATION/" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-800 underline" data-i18n="settings.advanced.pfsense.api_key_setup">Setup API Key</a>
|
|
</div>
|
|
<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 class="md:col-span-2">
|
|
<label class="block text-sm font-medium text-gray-700" for="pfSenseToken" data-i18n="settings.advanced.pfsense.token">API Key</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" placeholder="Your API key from System > REST API > Keys">
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.advanced.pfsense.token_hint">Generate in System > REST API > Keys in pfSense webConfigurator</p>
|
|
</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 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">
|
|
<a href="https://docs.opnsense.org/development/api.html" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-800 underline" data-i18n="settings.advanced.opnsense.api_docs">API Documentation</a>
|
|
</div>
|
|
<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="opnsenseBaseURL" data-i18n="settings.advanced.opnsense.base_url">Base URL</label>
|
|
<input id="opnsenseBaseURL" 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="opnsenseKey" data-i18n="settings.advanced.opnsense.key">API Key</label>
|
|
<input id="opnsenseKey" 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="Your API key">
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.advanced.opnsense.key_hint">Generate in System > Access > Users > API Keys</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="opnsenseSecret" data-i18n="settings.advanced.opnsense.secret">API Secret</label>
|
|
<input id="opnsenseSecret" 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="Your API secret">
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="settings.advanced.opnsense.secret_hint">Generate together with API key</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="opnsenseAlias" data-i18n="settings.advanced.opnsense.alias">Alias Name</label>
|
|
<input id="opnsenseAlias" 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="opnsenseSkipTLS" class="h-4 w-4 text-blue-600 border-gray-300 rounded">
|
|
<label for="opnsenseSkipTLS" class="ml-2 text-sm text-gray-700" data-i18n="settings.advanced.opnsense.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>
|
|
|
|
<!-- 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">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" id="emailAlertsForBans" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" onchange="updateEmailFieldsState()">
|
|
<span class="ml-2 text-sm text-gray-700" data-i18n="settings.email_alerts_for_bans">Enable email alerts for bans</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" id="emailAlertsForUnbans" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" onchange="updateEmailFieldsState()">
|
|
<span class="ml-2 text-sm text-gray-700" data-i18n="settings.email_alerts_for_unbans">Enable email alerts for unbans</span>
|
|
</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)">
|
|
<option value="builtin" data-i18n="settings.geoip_provider.builtin">Built-in (ip-api.com)</option>
|
|
<option value="maxmind" data-i18n="settings.geoip_provider.maxmind">MaxMind (Local Database)</option>
|
|
</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">
|
|
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" id="smtpFieldsContainer">
|
|
<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 disabled:bg-gray-100 disabled:cursor-not-allowed" 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 disabled:bg-gray-100 disabled:cursor-not-allowed">
|
|
<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 disabled:bg-gray-100 disabled:cursor-not-allowed" 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 disabled:bg-gray-100 disabled:cursor-not-allowed" 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 disabled:bg-gray-100 disabled:cursor-not-allowed" 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 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
<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 disabled:bg-gray-400 disabled:cursor-not-allowed" onclick="sendTestEmail()" id="sendTestEmailBtn" 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>
|
|
|
|
<!-- 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" />
|
|
<label for="defaultJailEnable" class="ml-2 block text-sm font-medium text-gray-700" data-i18n="settings.default_jail_enable">Enable Jails by Default</label>
|
|
</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>
|
|
<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>
|
|
<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>
|
|
<span id="filterFilePath" class="text-xs text-gray-500 font-mono" style="display: none;"></span>
|
|
</div>
|
|
<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>
|
|
<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>
|
|
<span id="jailFilePath" class="text-xs text-gray-500 font-mono" style="display: none;"></span>
|
|
</div>
|
|
<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">
|
|
<div class="mb-4 flex justify-end gap-2">
|
|
<button type="button" onclick="openCreateFilterModal()" class="inline-flex items-center gap-2 px-4 py-2 bg-gray-600 text-white text-sm font-medium rounded-md hover:bg-gray-700 transition-colors">
|
|
<i class="fas fa-plus"></i>
|
|
<span data-i18n="modal.create_filter">Create New Filter</span>
|
|
</button>
|
|
<button type="button" onclick="openCreateJailModal()" class="inline-flex items-center gap-2 px-4 py-2 bg-gray-600 text-white text-sm font-medium rounded-md hover:bg-gray-700 transition-colors">
|
|
<i class="fas fa-plus"></i>
|
|
<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>
|
|
</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>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 600px;">
|
|
<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.create_jail_title">Create New Jail</h3>
|
|
<button type="button" onclick="closeModal('createJailModal')" 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">
|
|
<div class="mb-4">
|
|
<label for="newJailName" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.jail_name">Jail Name</label>
|
|
<input type="text" id="newJailName" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., sshd" />
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="modal.jail_name_hint">Only alphanumeric characters, dashes, and underscores are allowed.</p>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="newJailFilter" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.jail_filter">Filter (optional)</label>
|
|
<select id="newJailFilter" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" onchange="updateJailConfigFromFilter()">
|
|
<option value="">-- Select a filter --</option>
|
|
</select>
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="modal.jail_filter_hint">Selecting a filter will auto-populate the jail configuration.</p>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="newJailContent" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.jail_config">Jail Configuration</label>
|
|
<textarea id="newJailContent" 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" placeholder="[jailname] enabled = false port = ssh filter = sshd logpath = /var/log/auth.log"></textarea>
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="modal.jail_config_hint">Jail configuration will be auto-populated when you select a filter.</p>
|
|
</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" onclick="createJail()" class="mt-3 w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" data-i18n="modal.create">Create</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('createJailModal')" data-i18n="modal.cancel">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
|
|
<div class="relative z-10 w-full rounded-lg bg-white text-left shadow-xl transition-all" style="max-width: 600px;">
|
|
<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.create_filter_title">Create New Filter</h3>
|
|
<button type="button" onclick="closeModal('createFilterModal')" 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">
|
|
<div class="mb-4">
|
|
<label for="newFilterName" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.filter_name">Filter Name</label>
|
|
<input type="text" id="newFilterName" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., myfilter" />
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="modal.filter_name_hint">Only alphanumeric characters, dashes, and underscores are allowed.</p>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="newFilterContent" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="modal.filter_config">Filter Configuration (optional)</label>
|
|
<textarea id="newFilterContent" 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" placeholder="# Filter: myfilter [Definition] failregex = ^.*Failed login.*$"></textarea>
|
|
<p class="text-xs text-gray-500 mt-1" data-i18n="modal.filter_config_hint">If left empty, an empty filter file will be created.</p>
|
|
</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" onclick="createFilter()" class="mt-3 w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" data-i18n="modal.create">Create</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('createFilterModal')" data-i18n="modal.cancel">Cancel</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" value="22" 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="sa_fail2ban">
|
|
</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="/config/.ssh/id_rsa">
|
|
<p class="mt-1 text-sm text-gray-500" data-i18n="servers.form.ssh_key_help">
|
|
Place your SSH private key in the <code class="px-1 py-0.5 bg-gray-100 rounded text-xs">/config/.ssh/</code> directory (mounted config volume).
|
|
The key file must have permissions <code class="px-1 py-0.5 bg-gray-100 rounded text-xs">600</code> (chmod 600).
|
|
Example: <code class="px-1 py-0.5 bg-gray-100 rounded text-xs">/config/.ssh/id_rsa</code>
|
|
</p>
|
|
</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>
|
|
</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="/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>
|
|
<script src="/static/js/utils.js?v={{.version}}"></script>
|
|
<script src="/static/js/validation.js?v={{.version}}"></script>
|
|
<script src="/static/js/modals.js?v={{.version}}"></script>
|
|
<script src="/static/js/translations.js?v={{.version}}"></script>
|
|
<script src="/static/js/ignoreips.js?v={{.version}}"></script>
|
|
<script src="/static/js/dashboard.js?v={{.version}}"></script>
|
|
<script src="/static/js/servers.js?v={{.version}}"></script>
|
|
<script src="/static/js/jails.js?v={{.version}}"></script>
|
|
<script src="/static/js/console.js?v={{.version}}"></script>
|
|
<script src="/static/js/settings.js?v={{.version}}"></script>
|
|
<script src="/static/js/filters.js?v={{.version}}"></script>
|
|
<script src="/static/js/websocket.js?v={{.version}}"></script>
|
|
<script src="/static/js/header.js?v={{.version}}"></script>
|
|
<script src="/static/js/lotr.js?v={{.version}}"></script>
|
|
<script src="/static/js/init.js?v={{.version}}"></script>
|
|
</body>
|
|
</html>
|