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:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -28,4 +28,9 @@ go.work.sum
|
||||
fail2ban-ui-settings.json
|
||||
_dev
|
||||
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
|
||||
COPY --from=builder /app/pkg/web/templates /app/templates
|
||||
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
|
||||
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**
|
||||
- Optimized for **mobile & desktop**
|
||||
- Powered by **Bootstrap 5**
|
||||
- Powered by **Tailwind CSS** (works offline when built locally)
|
||||
- **Go-based backend** ensures minimal resource usage
|
||||
- Parallel execution for improved performance on remote connections
|
||||
|
||||
@@ -91,10 +91,24 @@ To install and run directly on the system:
|
||||
```bash
|
||||
git clone https://github.com/swissmakers/fail2ban-ui.git /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
|
||||
...
|
||||
```
|
||||
|
||||
**📌 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**
|
||||
|
||||
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
|
||||
router.LoadHTMLGlob("/app/templates/*")
|
||||
router.Static("/locales", "/app/locales")
|
||||
router.Static("/static", "/app/static")
|
||||
} else {
|
||||
// When running locally, load templates from pkg/web/templates
|
||||
router.LoadHTMLGlob("pkg/web/templates/*")
|
||||
router.Static("/locales", "./internal/locales")
|
||||
router.Static("/static", "./pkg/web/static")
|
||||
}
|
||||
|
||||
// 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 name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||
<title data-i18n="page.title">Fail2ban UI Dashboard</title>
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Tailwind CSS - Try local first, fallback to CDN for development -->
|
||||
<link rel="stylesheet" href="/static/tailwind.css" onerror="
|
||||
console.warn('Local Tailwind CSS not found, using CDN. For production, build Tailwind CSS. See README.md for instructions.');
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.tailwindcss.com';
|
||||
document.head.appendChild(script);
|
||||
this.onerror = null;
|
||||
">
|
||||
<!-- Font Awesome for icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- Select2 CSS -->
|
||||
@@ -655,14 +661,14 @@
|
||||
<!-- ******************************************************************* -->
|
||||
<!-- Jail Config Modal -->
|
||||
<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="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<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="sm:flex sm:items-start">
|
||||
<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>
|
||||
</h3>
|
||||
<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>
|
||||
@@ -2512,11 +2537,39 @@
|
||||
//* 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) {
|
||||
currentJailForConfig = jailName;
|
||||
var textArea = document.getElementById('jailConfigTextarea');
|
||||
textArea.value = '';
|
||||
|
||||
// Prevent browser extensions from interfering
|
||||
preventExtensionInterference(textArea);
|
||||
|
||||
document.getElementById('modalJailName').textContent = jailName;
|
||||
|
||||
showLoading(true);
|
||||
@@ -2531,7 +2584,13 @@
|
||||
return;
|
||||
}
|
||||
textArea.value = data.config;
|
||||
// Prevent extension interference before opening modal
|
||||
preventExtensionInterference(textArea);
|
||||
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) {
|
||||
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