mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Switch to local-build tailwindcss instead CDN
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -29,3 +29,8 @@ fail2ban-ui-settings.json
|
|||||||
_dev
|
_dev
|
||||||
fail2ban-ui.db*
|
fail2ban-ui.db*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Node.js / Tailwind CSS build
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
.tailwind-build/
|
||||||
@@ -42,6 +42,8 @@ COPY --from=builder /app/fail2ban-ui /app/fail2ban-ui
|
|||||||
RUN chown fail2ban:0 /app/fail2ban-ui && chmod +x /app/fail2ban-ui
|
RUN chown fail2ban:0 /app/fail2ban-ui && chmod +x /app/fail2ban-ui
|
||||||
COPY --from=builder /app/pkg/web/templates /app/templates
|
COPY --from=builder /app/pkg/web/templates /app/templates
|
||||||
COPY --from=builder /app/internal/locales /app/locales
|
COPY --from=builder /app/internal/locales /app/locales
|
||||||
|
# Copy static files (Tailwind CSS) if they exist
|
||||||
|
COPY --from=builder /app/pkg/web/static /app/static
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV CONTAINER=true
|
ENV CONTAINER=true
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -55,7 +55,7 @@ Developed by **[Swissmakers GmbH](https://swissmakers.ch)**.
|
|||||||
|
|
||||||
✅ **Mobile-Friendly & Responsive UI / Fast**
|
✅ **Mobile-Friendly & Responsive UI / Fast**
|
||||||
- Optimized for **mobile & desktop**
|
- Optimized for **mobile & desktop**
|
||||||
- Powered by **Bootstrap 5**
|
- Powered by **Tailwind CSS** (works offline when built locally)
|
||||||
- **Go-based backend** ensures minimal resource usage
|
- **Go-based backend** ensures minimal resource usage
|
||||||
- Parallel execution for improved performance on remote connections
|
- Parallel execution for improved performance on remote connections
|
||||||
|
|
||||||
@@ -91,10 +91,24 @@ To install and run directly on the system:
|
|||||||
```bash
|
```bash
|
||||||
git clone https://github.com/swissmakers/fail2ban-ui.git /opt/fail2ban-ui
|
git clone https://github.com/swissmakers/fail2ban-ui.git /opt/fail2ban-ui
|
||||||
cd /opt/fail2ban-ui
|
cd /opt/fail2ban-ui
|
||||||
|
|
||||||
|
# Build Tailwind CSS for production (optional but recommended)
|
||||||
|
# This requires Node.js and npm to be installed
|
||||||
|
./build-tailwind.sh
|
||||||
|
|
||||||
|
# Build Go application
|
||||||
go build -o fail2ban-ui ./cmd/server/main.go
|
go build -o fail2ban-ui ./cmd/server/main.go
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**📌 Note on Tailwind CSS:**
|
||||||
|
- For **production/offline use**, build Tailwind CSS using `./build-tailwind.sh` before building the application
|
||||||
|
- This creates a local CSS file at `pkg/web/static/tailwind.css` that works offline
|
||||||
|
- The build script uses **Tailwind CSS v3** (latest v3.x, matches CDN version)
|
||||||
|
- If the local file is not found, the application will automatically fall back to the CDN (requires internet connection)
|
||||||
|
- The build script requires **Node.js** and **npm** to be installed
|
||||||
|
- The script works for both fresh installations and existing development environments
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### **🔹 Method 2: Running as a Container**
|
### **🔹 Method 2: Running as a Container**
|
||||||
|
|||||||
97
build-tailwind.sh
Executable file
97
build-tailwind.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build script for Tailwind CSS v3
|
||||||
|
# This script builds Tailwind CSS for production use
|
||||||
|
# Always installs latest Tailwind CSS v3 (matches CDN version)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Building Tailwind CSS v3 for Fail2ban-UI..."
|
||||||
|
|
||||||
|
# Check if Node.js and npm are installed
|
||||||
|
if ! command -v node &> /dev/null; then
|
||||||
|
echo "Error: Node.js is not installed. Please install Node.js first."
|
||||||
|
echo "Visit: https://nodejs.org/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v npm &> /dev/null; then
|
||||||
|
echo "Error: npm is not installed. Please install npm first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directories if they don't exist
|
||||||
|
mkdir -p pkg/web/static
|
||||||
|
mkdir -p .tailwind-build
|
||||||
|
|
||||||
|
# Initialize package.json if it doesn't exist
|
||||||
|
if [ ! -f package.json ]; then
|
||||||
|
echo "Initializing npm package..."
|
||||||
|
npm init -y --silent
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install latest Tailwind CSS v3
|
||||||
|
echo "Installing latest Tailwind CSS v3..."
|
||||||
|
npm install -D tailwindcss@^3 --silent
|
||||||
|
|
||||||
|
# Verify the CLI binary exists
|
||||||
|
if [ ! -f "node_modules/.bin/tailwindcss" ] && [ ! -f "node_modules/tailwindcss/lib/cli.js" ]; then
|
||||||
|
echo "❌ Error: Tailwind CSS CLI not found after installation."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show installed version
|
||||||
|
INSTALLED_VERSION=$(npm list tailwindcss 2>/dev/null | grep "tailwindcss@" | head -1 | awk '{print $2}' || echo "unknown")
|
||||||
|
echo "Installed: $INSTALLED_VERSION"
|
||||||
|
|
||||||
|
# Create tailwind.config.js if it doesn't exist
|
||||||
|
if [ ! -f tailwind.config.js ]; then
|
||||||
|
echo "Creating Tailwind CSS configuration..."
|
||||||
|
cat > tailwind.config.js << 'EOF'
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./pkg/web/templates/**/*.html",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create input CSS file if it doesn't exist
|
||||||
|
if [ ! -f .tailwind-build/input.css ]; then
|
||||||
|
echo "Creating input CSS file..."
|
||||||
|
cat > .tailwind-build/input.css << 'EOF'
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build Tailwind CSS
|
||||||
|
echo "Building Tailwind CSS..."
|
||||||
|
|
||||||
|
# Try different methods to run the CLI
|
||||||
|
if [ -f "node_modules/.bin/tailwindcss" ]; then
|
||||||
|
node_modules/.bin/tailwindcss -i .tailwind-build/input.css -o pkg/web/static/tailwind.css --minify
|
||||||
|
elif [ -f "node_modules/tailwindcss/lib/cli.js" ]; then
|
||||||
|
node node_modules/tailwindcss/lib/cli.js -i .tailwind-build/input.css -o pkg/web/static/tailwind.css --minify
|
||||||
|
elif command -v npx &> /dev/null; then
|
||||||
|
npx --yes tailwindcss -i .tailwind-build/input.css -o pkg/web/static/tailwind.css --minify
|
||||||
|
else
|
||||||
|
echo "❌ Error: Could not find Tailwind CSS CLI"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify output file was created and is not empty
|
||||||
|
if [ ! -f "pkg/web/static/tailwind.css" ] || [ ! -s "pkg/web/static/tailwind.css" ]; then
|
||||||
|
echo "❌ Error: Output file was not created or is empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Tailwind CSS v3 built successfully!"
|
||||||
|
echo "Output: pkg/web/static/tailwind.css"
|
||||||
|
echo ""
|
||||||
|
echo "The application will now use the local Tailwind CSS file instead of the CDN."
|
||||||
@@ -64,10 +64,12 @@ func main() {
|
|||||||
// In container, templates are assumed to be in /app/templates
|
// In container, templates are assumed to be in /app/templates
|
||||||
router.LoadHTMLGlob("/app/templates/*")
|
router.LoadHTMLGlob("/app/templates/*")
|
||||||
router.Static("/locales", "/app/locales")
|
router.Static("/locales", "/app/locales")
|
||||||
|
router.Static("/static", "/app/static")
|
||||||
} else {
|
} else {
|
||||||
// When running locally, load templates from pkg/web/templates
|
// When running locally, load templates from pkg/web/templates
|
||||||
router.LoadHTMLGlob("pkg/web/templates/*")
|
router.LoadHTMLGlob("pkg/web/templates/*")
|
||||||
router.Static("/locales", "./internal/locales")
|
router.Static("/locales", "./internal/locales")
|
||||||
|
router.Static("/static", "./pkg/web/static")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register all application routes, including the static files and templates.
|
// Register all application routes, including the static files and templates.
|
||||||
|
|||||||
23
package.json
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "fail2ban-ui",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "🚀 **Fail2Ban-UI** is a Swiss-made **web-based management interface** for [Fail2Ban](https://www.fail2ban.org/). It provides an intuitive dashboard to **monitor, configure, and manage Fail2Ban** instances in real time, supporting both local and remote Fail2ban servers.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/swissmakers/fail2ban-ui.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/swissmakers/fail2ban-ui/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/swissmakers/fail2ban-ui#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"tailwindcss": "^3.4.18"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
pkg/web/static/tailwind.css
Normal file
1
pkg/web/static/tailwind.css
Normal file
File diff suppressed because one or more lines are too long
@@ -22,8 +22,14 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||||
<title data-i18n="page.title">Fail2ban UI Dashboard</title>
|
<title data-i18n="page.title">Fail2ban UI Dashboard</title>
|
||||||
<!-- Tailwind CSS -->
|
<!-- Tailwind CSS - Try local first, fallback to CDN for development -->
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<link rel="stylesheet" href="/static/tailwind.css" onerror="
|
||||||
|
console.warn('Local Tailwind CSS not found, using CDN. For production, build Tailwind CSS. See README.md for instructions.');
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.src = 'https://cdn.tailwindcss.com';
|
||||||
|
document.head.appendChild(script);
|
||||||
|
this.onerror = null;
|
||||||
|
">
|
||||||
<!-- Font Awesome for icons -->
|
<!-- Font Awesome for icons -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<!-- Select2 CSS -->
|
<!-- Select2 CSS -->
|
||||||
@@ -655,14 +661,14 @@
|
|||||||
<!-- ******************************************************************* -->
|
<!-- ******************************************************************* -->
|
||||||
<!-- Jail Config Modal -->
|
<!-- Jail Config Modal -->
|
||||||
<div id="jailConfigModal" class="hidden fixed inset-0 overflow-y-auto" style="z-index: 60;">
|
<div id="jailConfigModal" class="hidden fixed inset-0 overflow-y-auto" style="z-index: 60;">
|
||||||
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
<div class="flex items-center justify-center min-h-screen pt-4 px-2 sm:px-4 pb-20 text-center">
|
||||||
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
||||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||||
|
|
||||||
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
<div class="relative inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all my-4 sm:my-8 align-middle w-full max-w-full" style="max-width: 90vw;">
|
||||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<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="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
||||||
@@ -670,7 +676,26 @@
|
|||||||
<span data-i18n="modal.filter_config">Filter Config:</span> <span id="modalJailName"></span>
|
<span data-i18n="modal.filter_config">Filter Config:</span> <span id="modalJailName"></span>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<textarea id="jailConfigTextarea" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-96 font-mono text-sm"></textarea>
|
<textarea id="jailConfigTextarea"
|
||||||
|
class="w-full border border-gray-700 rounded-md px-4 py-3 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 h-96 font-mono text-sm bg-gray-900 text-green-400 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: #4ade80; line-height: 1.5; tab-size: 2; width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box; -webkit-appearance: none; appearance: none;"
|
||||||
|
wrap="off"
|
||||||
|
onfocus="preventExtensionInterference(this);"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2512,11 +2537,39 @@
|
|||||||
//* Filter-mod and config-mod actions : *
|
//* Filter-mod and config-mod actions : *
|
||||||
//*******************************************************************
|
//*******************************************************************
|
||||||
|
|
||||||
|
// Prevent browser extensions (like iCloud Passwords) from interfering
|
||||||
|
function preventExtensionInterference(element) {
|
||||||
|
if (!element) return;
|
||||||
|
try {
|
||||||
|
// Ensure control property exists to prevent "Cannot read properties of undefined" errors
|
||||||
|
if (!element.control) {
|
||||||
|
Object.defineProperty(element, 'control', {
|
||||||
|
value: {
|
||||||
|
type: element.type || 'textarea',
|
||||||
|
name: element.name || 'filter-config-editor',
|
||||||
|
form: null,
|
||||||
|
autocomplete: 'off'
|
||||||
|
},
|
||||||
|
writable: false,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Prevent extensions from adding their own properties
|
||||||
|
Object.seal(element.control);
|
||||||
|
} catch (e) {
|
||||||
|
// Silently ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openJailConfigModal(jailName) {
|
function openJailConfigModal(jailName) {
|
||||||
currentJailForConfig = jailName;
|
currentJailForConfig = jailName;
|
||||||
var textArea = document.getElementById('jailConfigTextarea');
|
var textArea = document.getElementById('jailConfigTextarea');
|
||||||
textArea.value = '';
|
textArea.value = '';
|
||||||
|
|
||||||
|
// Prevent browser extensions from interfering
|
||||||
|
preventExtensionInterference(textArea);
|
||||||
|
|
||||||
document.getElementById('modalJailName').textContent = jailName;
|
document.getElementById('modalJailName').textContent = jailName;
|
||||||
|
|
||||||
showLoading(true);
|
showLoading(true);
|
||||||
@@ -2531,7 +2584,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
textArea.value = data.config;
|
textArea.value = data.config;
|
||||||
|
// Prevent extension interference before opening modal
|
||||||
|
preventExtensionInterference(textArea);
|
||||||
openModal('jailConfigModal');
|
openModal('jailConfigModal');
|
||||||
|
// Call again after a short delay to ensure it's set after modal is visible
|
||||||
|
setTimeout(function() {
|
||||||
|
preventExtensionInterference(textArea);
|
||||||
|
}, 100);
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
showToast("Error: " + err, 'error');
|
showToast("Error: " + err, 'error');
|
||||||
|
|||||||
10
tailwind.config.js
Normal file
10
tailwind.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./pkg/web/templates/**/*.html",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user