mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
1241 lines
52 KiB
HTML
1241 lines
52 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>
|
|
<!-- Bootstrap 5 (CDN) -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
<!-- Select2 CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet" />
|
|
<style>
|
|
/* Loading overlay styling */
|
|
#loading-overlay {
|
|
display: none; /* hidden by default */
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 9999; /* on top */
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.spinner-border {
|
|
width: 4rem;
|
|
height: 4rem;
|
|
}
|
|
|
|
/* Reload banner */
|
|
#reloadBanner {
|
|
display: none;
|
|
}
|
|
|
|
/* Improve table readability on small screens */
|
|
@media (max-width: 575px) {
|
|
.table th,
|
|
.table td {
|
|
font-size: 0.8rem;
|
|
padding: 5px;
|
|
}
|
|
.table thead {
|
|
font-size: 1rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body class="bg-light">
|
|
<!-- ******************************************************************* -->
|
|
<!-- NAVIGATION : -->
|
|
<!-- ******************************************************************* -->
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="#">
|
|
<strong>Fail2ban UI</strong>
|
|
</a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
|
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
|
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" onclick="showSection('dashboardSection')" data-i18n="nav.dashboard">Dashboard</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" onclick="showSection('filterSection')" data-i18n="nav.filter_debug">Filter Debug</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" onclick="showSection('settingsSection')" data-i18n="nav.settings">Settings</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<!-- ******************************************************************* -->
|
|
|
|
<!-- Reload Banner -->
|
|
<div id="reloadBanner" class="bg-warning text-dark p-3 text-center">
|
|
<strong data-i18n="reload_banner.message">Configuration changed!</strong>
|
|
<button class="btn btn-dark" onclick="reloadFail2ban()" data-i18n="reload_banner.button">Reload Fail2ban</button>
|
|
</div>
|
|
|
|
<!-- ******************************************************************* -->
|
|
<!-- APP Sections (Pages) : -->
|
|
<!-- ******************************************************************* -->
|
|
|
|
<!-- Dashboard Section -->
|
|
<div id="dashboardSection" class="container my-4">
|
|
<div class="d-flex align-items-center" style="position: relative;">
|
|
<h1 class="mb-4 flex-grow-1" data-i18n="dashboard.title">Dashboard</h1>
|
|
<button class="btn btn-outline-secondary" style="position: absolute; right: 0; top: 0;" onclick="openManageJailsModal()" data-i18n="dashboard.manage_jails">Manage Jails</button>
|
|
</div>
|
|
<div id="dashboard"></div>
|
|
</div>
|
|
|
|
<!-- Filter Debug Section -->
|
|
<div id="filterSection" style="display: none;" class="container my-4">
|
|
<h2 data-i18n="filter_debug.title">Filter Debug</h2>
|
|
<!-- Dropdown of available jail/filters -->
|
|
<div class="mb-3">
|
|
<label for="filterSelect" class="form-label" data-i18n="filter_debug.select_filter">Select a Filter</label>
|
|
<select id="filterSelect" class="form-select"></select>
|
|
</div>
|
|
<!-- Textarea for log lines to test -->
|
|
<div class="mb-3">
|
|
<label class="form-label" data-i18n="filter_debug.log_lines">Log Lines</label>
|
|
<textarea id="logLinesTextarea" class="form-control" rows="6" disabled
|
|
data-i18n-placeholder="filter_debug.log_lines_placeholder" placeholder="Enter log lines here..."></textarea>
|
|
</div>
|
|
<button class="btn btn-secondary" onclick="testSelectedFilter()" data-i18n="filter_debug.test_filter">Test Filter</button>
|
|
<hr/>
|
|
<div id="testResults"></div>
|
|
</div>
|
|
|
|
<!-- Settings Section -->
|
|
<div id="settingsSection" style="display: none;" class="container my-4">
|
|
<h2 data-i18n="settings.title">Settings</h2>
|
|
<form onsubmit="saveSettings(event)">
|
|
<!-- General Settings Group -->
|
|
<fieldset class="border p-3 rounded mb-4">
|
|
<legend class="w-auto px-2" data-i18n="settings.general">General Settings</legend>
|
|
<!-- Language Selection -->
|
|
<div class="mb-3">
|
|
<label for="languageSelect" class="form-label" data-i18n="settings.language">Language</label>
|
|
<select id="languageSelect" class="form-select">
|
|
<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-3">
|
|
<label for="uiPort" class="form-label" data-i18n="settings.server_port">Server Port</label>
|
|
<input type="number" class="form-control" id="uiPort"
|
|
data-i18n-placeholder="settings.server_port_placeholder" placeholder="e.g., 8080" required min="80" max="65535" required />
|
|
</div>
|
|
<!-- Debug Log Output -->
|
|
<div class="mb-3 form-check">
|
|
<input type="checkbox" class="form-check-input" id="debugMode">
|
|
<label for="debugMode" class="form-check-label" data-i18n="settings.enable_debug">Enable Debug Log</label>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<!-- Alert Settings Group -->
|
|
<fieldset class="border p-3 rounded mb-4">
|
|
<legend class="w-auto px-2" data-i18n="settings.alert">Alert Settings</legend>
|
|
<div class="mb-3">
|
|
<label for="destEmail" class="form-label" data-i18n="settings.destination_email">Destination Email (Alerts Receiver)</label>
|
|
<input type="email" class="form-control" id="destEmail"
|
|
data-i18n-placeholder="settings.destination_email_placeholder" placeholder="alerts@swissmakers.ch" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="alertCountries" class="form-label" data-i18n="settings.alert_countries">Alert Countries</label>
|
|
<p class="text-muted" 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="form-select" 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">Poland (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>
|
|
</fieldset>
|
|
|
|
<!-- SMTP Configuration Group -->
|
|
<fieldset class="border p-3 rounded mb-4">
|
|
<legend class="w-auto px-2" data-i18n="settings.smtp">SMTP Configuration</legend>
|
|
<div class="mb-3">
|
|
<label for="smtpHost" class="form-label" data-i18n="settings.smtp_host">SMTP Host</label>
|
|
<input type="text" class="form-control" id="smtpHost"
|
|
data-i18n-placeholder="settings.smtp_host_placeholder" placeholder="e.g., smtp.gmail.com" required />
|
|
</div>
|
|
<label for="smtpPort" data-i18n="settings.smtp_port">SMTP Port</label>
|
|
<select id="smtpPort" class="form-select">
|
|
<option value="587" selected>587 (Recommended - STARTTLS)</option>
|
|
<option value="465" disabled>465 (Not Supported)</option>
|
|
</select>
|
|
<div class="mb-3">
|
|
<label for="smtpUsername" class="form-label" data-i18n="settings.smtp_username">SMTP Username</label>
|
|
<input type="text" class="form-control" id="smtpUsername"
|
|
data-i18n-placeholder="settings.smtp_username_placeholder" placeholder="e.g., user@example.com" required />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="smtpPassword" class="form-label" data-i18n="settings.smtp_password">SMTP Password</label>
|
|
<input type="password" class="form-control" id="smtpPassword"
|
|
data-i18n-placeholder="settings.smtp_password_placeholder" placeholder="Enter SMTP Password" required />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="smtpFrom" class="form-label" data-i18n="settings.smtp_sender">Sender Email</label>
|
|
<input type="email" class="form-control" id="smtpFrom"
|
|
data-i18n-placeholder="settings.smtp_sender_placeholder" placeholder="noreply@swissmakers.ch" required />
|
|
</div>
|
|
<div class="mb-3 form-check">
|
|
<input type="checkbox" class="form-check-input" id="smtpUseTLS">
|
|
<label for="smtpUseTLS" class="form-check-label" data-i18n="settings.smtp_tls">Use TLS (Recommended)</label>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary mt-2" onclick="sendTestEmail()" data-i18n="settings.send_test_email">Send Test Email</button>
|
|
</fieldset>
|
|
|
|
<!-- Fail2Ban Configuration Group -->
|
|
<fieldset class="border p-3 rounded mb-4">
|
|
<legend class="w-auto px-2" data-i18n="settings.fail2ban">Fail2Ban Configuration</legend>
|
|
<!-- Bantime Increment -->
|
|
<div class="mb-3 form-check">
|
|
<input type="checkbox" class="form-check-input" id="bantimeIncrement" />
|
|
<label for="bantimeIncrement" class="form-check-label" data-i18n="settings.enable_bantime_increment">Enable Bantime Increment</label>
|
|
</div>
|
|
<!-- Bantime -->
|
|
<div class="mb-3">
|
|
<label for="banTime" class="form-label" data-i18n="settings.default_bantime">Default Bantime</label>
|
|
<input type="text" class="form-control" id="banTime"
|
|
data-i18n-placeholder="settings.default_bantime_placeholder" placeholder="e.g., 48h" />
|
|
</div>
|
|
<!-- Findtime -->
|
|
<div class="mb-3">
|
|
<label for="findTime" class="form-label" data-i18n="settings.default_findtime">Default Findtime</label>
|
|
<input type="text" class="form-control" id="findTime"
|
|
data-i18n-placeholder="settings.default_findtime_placeholder" placeholder="e.g., 30m" />
|
|
</div>
|
|
<!-- Max Retry -->
|
|
<div class="mb-3">
|
|
<label for="maxRetry" class="form-label" data-i18n="settings.default_max_retry">Default Max Retry</label>
|
|
<input type="number" class="form-control" id="maxRetry"
|
|
data-i18n-placeholder="settings.default_max_retry_placeholder" placeholder="Enter maximum retries" />
|
|
</div>
|
|
<!-- Ignore IPs -->
|
|
<div class="mb-3">
|
|
<label for="ignoreIP" class="form-label" data-i18n="settings.ignore_ips">Ignore IPs</label>
|
|
<textarea class="form-control" id="ignoreIP" rows="2"
|
|
data-i18n-placeholder="settings.ignore_ips_placeholder" placeholder="IPs to ignore, separated by spaces"></textarea>
|
|
</div>
|
|
</fieldset>
|
|
<button type="submit" class="btn btn-primary" data-i18n="settings.save">Save</button>
|
|
</form>
|
|
</div>
|
|
<!-- ******************************************************************* -->
|
|
|
|
<!-- Footer -->
|
|
<footer class="text-center mt-4 mb-4">
|
|
<p class="mb-0">
|
|
© <a href="https://swissmakers.ch" target="_blank">Swissmakers GmbH</a>
|
|
-
|
|
<a href="https://github.com/swissmakers/fail2ban-ui" target="_blank">GitHub</a>
|
|
</p>
|
|
</footer>
|
|
|
|
<!-- ******************************************************************* -->
|
|
<!-- APP Components (HTML) : -->
|
|
<!-- ******************************************************************* -->
|
|
<!-- Loading Overlay -->
|
|
<div id="loading-overlay" class="d-flex">
|
|
<div class="spinner-border text-light" role="status">
|
|
<span class="visually-hidden" data-i18n="loading">Loading...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jail Config Modal -->
|
|
<div class="modal fade" id="jailConfigModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<span data-i18n="modal.filter_config">Filter Config:</span> <span id="modalJailName"></span>
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<textarea id="jailConfigTextarea" class="form-control" rows="15"></textarea>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="modal.cancel">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveJailConfig()" data-i18n="modal.save">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manage Jails Modal -->
|
|
<div class="modal fade" id="manageJailsModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" data-i18n="modal.manage_jails_title">Manage Jails</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Dynamically filled list of jails with toggle switches -->
|
|
<div id="jailsList"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="modal.cancel">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveManageJails()" data-i18n="modal.save">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- ******************************************************************* -->
|
|
|
|
<!-- Bootstrap 5 JS (for modal, etc.) -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<!-- 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>
|
|
|
|
<script>
|
|
// For information: We avoid ES6 backticks in our JS, to prevent confusion with the Go template parser.
|
|
"use strict";
|
|
|
|
//*******************************************************************
|
|
//* Init page and main-components : *
|
|
//*******************************************************************
|
|
|
|
var currentJailForConfig = null;
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
showLoading(true);
|
|
checkReloadNeeded();
|
|
fetchSummary().then(function() {
|
|
showLoading(false);
|
|
initializeTooltips(); // Initialize tooltips after fetching and rendering
|
|
getTranslationsSettingsOnPageload();
|
|
});
|
|
});
|
|
|
|
// Function to initialize Bootstrap tooltips
|
|
function initializeTooltips() {
|
|
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
|
new bootstrap.Tooltip(tooltipTriggerEl);
|
|
});
|
|
}
|
|
|
|
// Toggle the loading overlay (with !important)
|
|
function showLoading(show) {
|
|
var overlay = document.getElementById('loading-overlay');
|
|
if (show) {
|
|
overlay.style.setProperty('display', 'flex', 'important');
|
|
} else {
|
|
overlay.style.setProperty('display', 'none', 'important');
|
|
}
|
|
}
|
|
|
|
// Check if there is still a reload of the fail2ban service needed
|
|
function checkReloadNeeded() {
|
|
fetch('/api/settings')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.reloadNeeded) {
|
|
document.getElementById('reloadBanner').style.display = 'block';
|
|
} else {
|
|
document.getElementById('reloadBanner').style.display = 'none';
|
|
}
|
|
})
|
|
.catch(err => console.error('Error checking reloadNeeded:', err));
|
|
}
|
|
|
|
// Load dynamically the other pages when navigating in nav
|
|
function showSection(sectionId) {
|
|
// hide all sections
|
|
document.getElementById('dashboardSection').style.display = 'none';
|
|
document.getElementById('filterSection').style.display = 'none';
|
|
document.getElementById('settingsSection').style.display = 'none';
|
|
|
|
// show the requested section
|
|
document.getElementById(sectionId).style.display = 'block';
|
|
|
|
// If it's filterSection, load filters
|
|
if (sectionId === 'filterSection') {
|
|
showFilterSection();
|
|
}
|
|
// If it's settingsSection, load settings
|
|
if (sectionId === 'settingsSection') {
|
|
loadSettings();
|
|
}
|
|
|
|
// Close navbar on mobile when clicking a menu item
|
|
let navbar = document.getElementById('navbarSupportedContent');
|
|
if (navbar.classList.contains('show')) {
|
|
let navbarToggler = document.querySelector('.navbar-toggler');
|
|
navbarToggler.click();
|
|
}
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Fetch data and render dashboard : *
|
|
//*******************************************************************
|
|
|
|
// Fetch summary (jails, stats, last bans)
|
|
function fetchSummary() {
|
|
return fetch('/api/summary')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
document.getElementById('dashboard').innerHTML =
|
|
'<div class="alert alert-danger">' + data.error + '</div>';
|
|
return;
|
|
}
|
|
renderDashboard(data);
|
|
})
|
|
.catch(function(err) {
|
|
document.getElementById('dashboard').innerHTML =
|
|
'<div class="alert alert-danger">Error: ' + err + '</div>';
|
|
});
|
|
}
|
|
|
|
// Render the main dashboard
|
|
function renderDashboard(data) {
|
|
var html = "";
|
|
|
|
// Add a search bar
|
|
html += `
|
|
<fieldset class="border p-3 rounded mb-4">
|
|
<legend class="w-auto px-2"><span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The Overview displays the currently enabled jails that you have added to your jail.local configuration." data-i18n="dashboard.overview">Overview active Jails and Blocks</span></legend>
|
|
<div class="mb-3">
|
|
<label for="ipSearch" class="form-label" data-i18n="dashboard.search_label">Search Banned IPs</label>
|
|
<input type="text" id="ipSearch" class="form-control" placeholder="Enter IP address to search" data-i18n-placeholder="dashboard.search_placeholder" onkeyup="filterIPs()">
|
|
</div>
|
|
`;
|
|
|
|
// Jails table
|
|
if (!data.jails || data.jails.length === 0) {
|
|
html += '<p data-i18n="dashboard.no_jails">No jails found.</p>';
|
|
} else {
|
|
html += ''
|
|
+ '<div class="table-responsive">'
|
|
+ '<table class="table table-striped" id="jailsTable">'
|
|
+ ' <thead>'
|
|
+ ' <tr>'
|
|
+ ' <th data-i18n="dashboard.table.jail_name">Jail Name</th>'
|
|
+ ' <th data-i18n="dashboard.table.total_banned">Total Banned</th>'
|
|
+ ' <th data-i18n="dashboard.table.new_last_hour">New Last Hour</th>'
|
|
+ ' <th data-i18n="dashboard.table.banned_ips">Banned IPs (Unban)</th>'
|
|
+ ' </tr>'
|
|
+ ' </thead>'
|
|
+ ' <tbody>';
|
|
|
|
data.jails.forEach(function(jail) {
|
|
var bannedHTML = renderBannedIPs(jail.jailName, jail.bannedIPs);
|
|
html += ''
|
|
+ '<tr class="jail-row">'
|
|
+ ' <td>'
|
|
+ ' <a href="#" onclick="openJailConfigModal(\'' + jail.jailName + '\')">'
|
|
+ jail.jailName
|
|
+ ' </a>'
|
|
+ ' </td>'
|
|
+ ' <td>' + jail.totalBanned + '</td>'
|
|
+ ' <td>' + jail.newInLastHour + '</td>'
|
|
+ ' <td>' + bannedHTML + '</td>'
|
|
+ '</tr>';
|
|
});
|
|
|
|
html += '</tbody></table>';
|
|
html += '</div></fieldset>';
|
|
}
|
|
|
|
// Last 5 bans
|
|
html += '<fieldset class="border p-3 rounded mb-4">';
|
|
html += ' <legend class="w-auto px-2" data-i18n="dashboard.last_bans">Last 5 Ban Events</legend>';
|
|
if (!data.lastBans || data.lastBans.length === 0) {
|
|
html += '<p data-i18n="dashboard.no_recent_bans">No recent bans found.</p>';
|
|
} else {
|
|
html += ''
|
|
+ '<div class="table-responsive">'
|
|
+ '<table class="table table-bordered">'
|
|
+ ' <thead>'
|
|
+ ' <tr>'
|
|
+ ' <th data-i18n="dashboard.table.time">Time</th>'
|
|
+ ' <th data-i18n="dashboard.table.jail">Jail</th>'
|
|
+ ' <th data-i18n="dashboard.table.ip">IP</th>'
|
|
+ ' <th data-i18n="dashboard.table.log_line">Log Line</th>'
|
|
+ ' </tr>'
|
|
+ ' </thead>'
|
|
+ ' <tbody></div>';
|
|
|
|
data.lastBans.forEach(function(e) {
|
|
html += ''
|
|
+ '<tr>'
|
|
+ ' <td>' + e.Time + '</td>'
|
|
+ ' <td>' + e.Jail + '</td>'
|
|
+ ' <td>' + e.IP + '</td>'
|
|
+ ' <td>' + e.LogLine + '</td>'
|
|
+ '</tr>';
|
|
});
|
|
|
|
html += '</tbody></table></fieldset>';
|
|
}
|
|
|
|
document.getElementById('dashboard').innerHTML = html;
|
|
}
|
|
|
|
// Render banned IPs with "Unban" button
|
|
function renderBannedIPs(jailName, ips) {
|
|
if (!ips || ips.length === 0) {
|
|
return '<em data-i18n="dashboard.no_banned_ips">No banned IPs</em>';
|
|
}
|
|
var content = '<ul class="list-unstyled mb-0">';
|
|
ips.forEach(function(ip) {
|
|
content += ''
|
|
+ '<li class="d-flex align-items-center mb-1">'
|
|
+ ' <span class="me-auto">' + ip + '</span>'
|
|
+ ' <button class="btn btn-sm btn-warning"'
|
|
+ ' onclick="unbanIP(\'' + jailName + '\', \'' + ip + '\')">'
|
|
+ ' <span data-i18n="dashboard.unban">Unban</span>'
|
|
+ ' </button>'
|
|
+ '</li>';
|
|
});
|
|
content += '</ul>';
|
|
return content;
|
|
}
|
|
|
|
// Filter IPs on dashboard table
|
|
function filterIPs() {
|
|
const query = document.getElementById("ipSearch").value.toLowerCase();
|
|
const rows = document.querySelectorAll("#jailsTable .jail-row");
|
|
|
|
rows.forEach((row) => {
|
|
const ipSpans = row.querySelectorAll("ul li span");
|
|
let matchFound = false;
|
|
|
|
ipSpans.forEach((span) => {
|
|
const originalText = span.textContent;
|
|
const ipText = originalText.toLowerCase();
|
|
|
|
if (query && ipText.includes(query)) {
|
|
matchFound = true;
|
|
const highlightedText = originalText.replace(
|
|
new RegExp(query, "gi"),
|
|
(match) => `<mark>${match}</mark>`
|
|
);
|
|
span.innerHTML = highlightedText;
|
|
} else {
|
|
span.innerHTML = originalText;
|
|
}
|
|
});
|
|
|
|
row.style.display = matchFound || !query ? "" : "none";
|
|
});
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Functions to manage IP-bans : *
|
|
//*******************************************************************
|
|
|
|
function unbanIP(jail, ip) {
|
|
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
|
|
return;
|
|
}
|
|
showLoading(true);
|
|
fetch('/api/jails/' + jail + '/unban/' + ip, { method: 'POST' })
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
alert("Error: " + data.error);
|
|
} else {
|
|
alert(data.message || "IP unbanned successfully");
|
|
}
|
|
return fetchSummary();
|
|
})
|
|
.catch(function(err) {
|
|
alert("Error: " + err);
|
|
})
|
|
.finally(function() {
|
|
showLoading(false);
|
|
});
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Filter-mod and config-mod actions : *
|
|
//*******************************************************************
|
|
|
|
function openJailConfigModal(jailName) {
|
|
currentJailForConfig = jailName;
|
|
var textArea = document.getElementById('jailConfigTextarea');
|
|
textArea.value = '';
|
|
|
|
document.getElementById('modalJailName').textContent = jailName;
|
|
|
|
showLoading(true);
|
|
fetch('/api/jails/' + jailName + '/config')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
alert("Error loading config: " + data.error);
|
|
} else {
|
|
textArea.value = data.config;
|
|
var modalEl = document.getElementById('jailConfigModal');
|
|
var myModal = new bootstrap.Modal(modalEl);
|
|
myModal.show();
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
alert("Error: " + err);
|
|
})
|
|
.finally(function() {
|
|
showLoading(false);
|
|
});
|
|
}
|
|
|
|
function saveJailConfig() {
|
|
if (!currentJailForConfig) return;
|
|
showLoading(true);
|
|
|
|
var newConfig = document.getElementById('jailConfigTextarea').value;
|
|
fetch('/api/jails/' + currentJailForConfig + '/config', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ config: newConfig }),
|
|
})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
alert("Error saving config: " + data.error);
|
|
} else {
|
|
console.log("Filter saved successfully. Reload needed? " + data.reloadNeeded);
|
|
var modalEl = document.getElementById('jailConfigModal');
|
|
var modalObj = bootstrap.Modal.getInstance(modalEl);
|
|
modalObj.hide();
|
|
document.getElementById('reloadBanner').style.display = 'block';
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
alert("Error: " + err);
|
|
})
|
|
.finally(function() {
|
|
showLoading(false);
|
|
});
|
|
}
|
|
|
|
// Function: openManageJailsModal
|
|
// Fetches the full-list of all jails (from /jails/manage) and builds a list with toggle switches.
|
|
function openManageJailsModal() {
|
|
showLoading(true);
|
|
fetch('/api/jails/manage')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (!data.jails || data.jails.length === 0) {
|
|
alert("No jails found.");
|
|
showLoading(false);
|
|
return;
|
|
}
|
|
var html = '<div class="list-group">';
|
|
data.jails.forEach(function(jail) {
|
|
var isEnabled = (jail.enabled === true);
|
|
html += '<div class="list-group-item d-flex justify-content-between align-items-center">';
|
|
html += '<span>' + jail.jailName + '</span>';
|
|
html += '<div class="form-check form-switch">';
|
|
html += '<input class="form-check-input" type="checkbox" id="toggle-' + jail.jailName + '" ' + (isEnabled ? 'checked' : '') + '>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
});
|
|
html += '</div>';
|
|
document.getElementById('jailsList').innerHTML = html;
|
|
var modalEl = document.getElementById('manageJailsModal');
|
|
var manageJailsModal = new bootstrap.Modal(modalEl);
|
|
manageJailsModal.show();
|
|
})
|
|
.catch(function(err) {
|
|
alert("Error fetching jails: " + err);
|
|
})
|
|
.finally(function() {
|
|
showLoading(false);
|
|
});
|
|
}
|
|
|
|
// Function: saveManageJails
|
|
// Collects the toggled states from the Manage Jails modal and sends updates to the API.
|
|
function saveManageJails() {
|
|
showLoading(true);
|
|
var updatedJails = {};
|
|
$('.list-group-item').each(function() {
|
|
var jailName = $(this).find('span').text();
|
|
var isEnabled = $(this).find('input[type="checkbox"]').is(':checked');
|
|
updatedJails[jailName] = isEnabled;
|
|
});
|
|
// Send updated states to the API endpoint /api/jails/manage.
|
|
fetch('/api/jails/manage', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(updatedJails),
|
|
})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
alert("Error saving jail settings: " + data.error);
|
|
} else {
|
|
// A restart of fail2ban is needed, to enable or disable jails - a reload is not enough
|
|
document.getElementById('reloadBanner').style.display = 'block';
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
alert("Error: " + err);
|
|
})
|
|
.finally(function() {
|
|
showLoading(false);
|
|
var modalEl = document.getElementById('manageJailsModal');
|
|
var manageJailsModal = bootstrap.Modal.getInstance(modalEl);
|
|
if (manageJailsModal) {
|
|
manageJailsModal.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Load current settings when opening settings page : *
|
|
//*******************************************************************
|
|
|
|
function loadSettings() {
|
|
showLoading(true);
|
|
fetch('/api/settings')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
document.getElementById('languageSelect').value = data.language || 'en';
|
|
document.getElementById('uiPort').value = data.port || 8080,
|
|
document.getElementById('debugMode').checked = data.debug || false;
|
|
|
|
document.getElementById('destEmail').value = data.destemail || '';
|
|
|
|
const select = document.getElementById('alertCountries');
|
|
for (let i = 0; i < select.options.length; i++) {
|
|
select.options[i].selected = false;
|
|
}
|
|
if (!data.alertCountries || data.alertCountries.length === 0) {
|
|
select.options[0].selected = true;
|
|
} else {
|
|
for (let i = 0; i < select.options.length; i++) {
|
|
let val = select.options[i].value;
|
|
if (data.alertCountries.includes(val)) {
|
|
select.options[i].selected = true;
|
|
}
|
|
}
|
|
}
|
|
$('#alertCountries').trigger('change');
|
|
|
|
if (data.smtp) {
|
|
document.getElementById('smtpHost').value = data.smtp.host || '';
|
|
document.getElementById('smtpPort').value = data.smtp.port || 587;
|
|
document.getElementById('smtpUsername').value = data.smtp.username || '';
|
|
document.getElementById('smtpPassword').value = data.smtp.password || '';
|
|
document.getElementById('smtpFrom').value = data.smtp.from || '';
|
|
document.getElementById('smtpUseTLS').checked = data.smtp.useTLS || false;
|
|
}
|
|
|
|
document.getElementById('bantimeIncrement').checked = data.bantimeIncrement || false;
|
|
document.getElementById('banTime').value = data.bantime || '';
|
|
document.getElementById('findTime').value = data.findtime || '';
|
|
document.getElementById('maxRetry').value = data.maxretry || '';
|
|
document.getElementById('ignoreIP').value = data.ignoreip || '';
|
|
})
|
|
.catch(err => {
|
|
alert('Error loading settings: ' + err);
|
|
})
|
|
.finally(() => showLoading(false));
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Save settings when hitting the save button : *
|
|
//*******************************************************************
|
|
|
|
function saveSettings(event) {
|
|
event.preventDefault();
|
|
showLoading(true);
|
|
|
|
const smtpSettings = {
|
|
host: document.getElementById('smtpHost').value.trim(),
|
|
port: parseInt(document.getElementById('smtpPort').value, 10) || 587,
|
|
username: document.getElementById('smtpUsername').value.trim(),
|
|
password: document.getElementById('smtpPassword').value.trim(),
|
|
from: document.getElementById('smtpFrom').value.trim(),
|
|
useTLS: document.getElementById('smtpUseTLS').checked,
|
|
};
|
|
|
|
const selectedCountries = Array.from(document.getElementById('alertCountries').selectedOptions).map(opt => opt.value);
|
|
|
|
const settingsData = {
|
|
language: document.getElementById('languageSelect').value,
|
|
port: parseInt(document.getElementById('uiPort').value, 10) || 8080,
|
|
debug: document.getElementById('debugMode').checked,
|
|
destemail: document.getElementById('destEmail').value.trim(),
|
|
alertCountries: selectedCountries.length > 0 ? selectedCountries : ["ALL"],
|
|
bantimeIncrement: document.getElementById('bantimeIncrement').checked,
|
|
bantime: document.getElementById('banTime').value.trim(),
|
|
findtime: document.getElementById('findTime').value.trim(),
|
|
maxretry: parseInt(document.getElementById('maxRetry').value, 10) || 3,
|
|
ignoreip: document.getElementById('ignoreIP').value.trim(),
|
|
smtp: smtpSettings
|
|
};
|
|
|
|
fetch('/api/settings', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(settingsData),
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
alert('Error saving settings: ' + data.error + data.details);
|
|
} else {
|
|
var selectedLang = $('#languageSelect').val();
|
|
loadTranslations(selectedLang);
|
|
console.log("Settings saved successfully. Reload needed? " + data.reloadNeeded);
|
|
if (data.reloadNeeded) {
|
|
document.getElementById('reloadBanner').style.display = 'block';
|
|
}
|
|
}
|
|
})
|
|
.catch(err => alert('Error: ' + err))
|
|
.finally(() => showLoading(false));
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Load the list of filters from /api/filters : *
|
|
//*******************************************************************
|
|
|
|
function loadFilters() {
|
|
showLoading(true);
|
|
fetch('/api/filters')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
alert('Error loading filters: ' + data.error);
|
|
return;
|
|
}
|
|
const select = document.getElementById('filterSelect');
|
|
select.innerHTML = '';
|
|
if (!data.filters || data.filters.length === 0) {
|
|
const opt = document.createElement('option');
|
|
opt.value = '';
|
|
opt.textContent = 'No Filters Found';
|
|
select.appendChild(opt);
|
|
} else {
|
|
data.filters.forEach(f => {
|
|
const opt = document.createElement('option');
|
|
opt.value = f;
|
|
opt.textContent = f;
|
|
select.appendChild(opt);
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
alert('Error loading filters: ' + err);
|
|
})
|
|
.finally(() => showLoading(false));
|
|
}
|
|
|
|
function sendTestEmail() {
|
|
showLoading(true);
|
|
|
|
fetch('/api/settings/test-email', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
alert('Error sending test email: ' + data.error);
|
|
} else {
|
|
alert('Test email sent successfully!');
|
|
}
|
|
})
|
|
.catch(error => alert('Error: ' + error))
|
|
.finally(() => showLoading(false));
|
|
}
|
|
|
|
// Called when clicking "Test Filter" button
|
|
function testSelectedFilter() {
|
|
const filterName = document.getElementById('filterSelect').value;
|
|
const lines = document.getElementById('logLinesTextarea').value.split('\n');
|
|
|
|
if (!filterName) {
|
|
alert('Please select a filter.');
|
|
return;
|
|
}
|
|
|
|
showLoading(true);
|
|
fetch('/api/filters/test', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
filterName: filterName,
|
|
logLines: lines
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
alert('Error: ' + data.error);
|
|
return;
|
|
}
|
|
renderTestResults(data.matches);
|
|
})
|
|
.catch(err => {
|
|
alert('Error: ' + err);
|
|
})
|
|
.finally(() => showLoading(false));
|
|
}
|
|
|
|
function renderTestResults(matches) {
|
|
let html = '<h5 data-i18n="filter_debug.test_results_title">Test Results</h5>';
|
|
if (!matches || matches.length === 0) {
|
|
html += '<p data-i18n="filter_debug.no_matches">No matches found.</p>';
|
|
} else {
|
|
html += '<ul>';
|
|
matches.forEach(m => {
|
|
html += '<li>' + m + '</li>';
|
|
});
|
|
html += '</ul>';
|
|
}
|
|
document.getElementById('testResults').innerHTML = html;
|
|
}
|
|
|
|
// When showing the filter section
|
|
function showFilterSection() {
|
|
loadFilters();
|
|
document.getElementById('testResults').innerHTML = '';
|
|
document.getElementById('logLinesTextarea').value = '';
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Reload fail2ban action : *
|
|
//*******************************************************************
|
|
|
|
function reloadFail2ban() {
|
|
if (!confirm("It can happen that some logs are not parsed during the reload of fail2ban. Reload Fail2ban now?")) return;
|
|
showLoading(true);
|
|
fetch('/api/fail2ban/reload', { method: 'POST' })
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
alert("Error: " + data.error);
|
|
} else {
|
|
document.getElementById('reloadBanner').style.display = 'none';
|
|
return fetchSummary();
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
alert("Error: " + err);
|
|
})
|
|
.finally(function() {
|
|
showLoading(false);
|
|
});
|
|
}
|
|
|
|
//*******************************************************************
|
|
//* Is executed when doc-ready : *
|
|
//*******************************************************************
|
|
|
|
$(document).ready(function() {
|
|
$('#alertCountries').select2({
|
|
placeholder: 'Select countries..',
|
|
allowClear: true,
|
|
width: '100%'
|
|
});
|
|
|
|
$('#alertCountries').on('select2:select', function(e) {
|
|
var selectedValue = e.params.data.id;
|
|
var currentValues = $('#alertCountries').val() || [];
|
|
if (selectedValue === 'ALL') {
|
|
if (currentValues.length > 1) {
|
|
$('#alertCountries').val(['ALL']).trigger('change');
|
|
}
|
|
} else {
|
|
if (currentValues.indexOf('ALL') !== -1) {
|
|
var newValues = currentValues.filter(function(value) {
|
|
return value !== 'ALL';
|
|
});
|
|
$('#alertCountries').val(newValues).trigger('change');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
//*******************************************************************
|
|
//* Translation Related Functions : *
|
|
//*******************************************************************
|
|
var translations = {};
|
|
|
|
// Loads translation JSON file for given language (e.g., en, de, etc.)
|
|
function loadTranslations(lang) {
|
|
$.getJSON('/locales/' + lang + '.json')
|
|
.done(function(data) {
|
|
translations = data;
|
|
updateTranslations();
|
|
})
|
|
.fail(function() {
|
|
console.error('Failed to load translations for language:', lang);
|
|
});
|
|
}
|
|
|
|
// Updates all elements with data-i18n attribute with corresponding translation.
|
|
function updateTranslations() {
|
|
$('[data-i18n]').each(function() {
|
|
var key = $(this).data('i18n');
|
|
if (translations[key]) {
|
|
$(this).text(translations[key]);
|
|
}
|
|
});
|
|
// Updates placeholders.
|
|
$('[data-i18n-placeholder]').each(function() {
|
|
var key = $(this).data('i18n-placeholder');
|
|
if (translations[key]) {
|
|
$(this).attr('placeholder', translations[key]);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getTranslationsSettingsOnPageload() {
|
|
// Fetch settings to get the current language preference
|
|
fetch('/api/settings')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
var lang = data.language || 'en'; // Use the language from settings or default to "en"
|
|
$('#languageSelect').val(lang); // Update the language dropdown accordingly
|
|
loadTranslations(lang); // Load the appropriate translation file
|
|
})
|
|
.catch(function(err) {
|
|
console.error('Error loading initial settings:', err);
|
|
// In case of an error, fallback to English
|
|
loadTranslations('en');
|
|
});
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html> |