This commit is contained in:
Mateusz Gruszczyński
2025-11-01 21:25:53 +01:00
parent 36be2db418
commit 3c8b9b062d
14 changed files with 1276 additions and 0 deletions

9
static/css/main.css Normal file
View File

@@ -0,0 +1,9 @@
:root{--radius:1rem}.card{border-radius:var(--radius)}.form-control,.form-select,.btn{border-radius:.75rem}.table{border-color:rgba(255,255,255,.1)}.badge{border-radius:.5rem}
/* UX enhancements */
.breadcrumb{--bs-breadcrumb-divider: '';}
main.container{scroll-margin-top: 4rem;}
.toast-container{z-index: 1080}
.btn .bi{margin-right:.35rem}
.form-progress{height:4px}
.spinner-inline{display:inline-flex;align-items:center;gap:.5rem}

95
static/js/main.js Normal file
View File

@@ -0,0 +1,95 @@
// Bootstrap helpers
(() => {
'use strict';
// 1) Show Flask flash messages as Bootstrap toasts
const flashes = document.getElementById('_flash_msgs');
if (flashes) {
try{
const msgs = JSON.parse(flashes.dataset.msgs || "[]");
const stack = document.getElementById('toast-stack');
msgs.forEach((m) => {
const el = document.createElement('div');
el.className = 'toast align-items-center text-bg-primary border-0';
el.role = 'alert'; el.ariaLive = 'assertive'; el.ariaAtomic = 'true';
el.innerHTML = `<div class="d-flex">
<div class="toast-body"><i class="bi bi-info-circle me-2"></i>${m}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Zamknij"></button>
</div>`;
stack.appendChild(el);
const t = new bootstrap.Toast(el, { delay: 3500 });
t.show();
});
}catch(e){ console.warn('Toast parse error', e); }
}
// 2) Client-side validation (Bootstrap)
const forms = document.querySelectorAll('form');
Array.from(forms).forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
// 3) Micro-loader on submit
const submitBtn = form.querySelector('[type="submit"]');
if (submitBtn && !submitBtn.dataset.loading) {
submitBtn.dataset.loading = '1';
submitBtn.disabled = true;
const original = submitBtn.innerHTML;
submitBtn.dataset.original = original;
submitBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Przetwarzanie...`;
}
}
form.classList.add('was-validated');
}, false);
});
// 4) Auto-accordion for long forms:
// If a form contains multiple H2/H3 sections, group content between them.
const targetForms = document.querySelectorAll('form');
targetForms.forEach(form => {
const headers = Array.from(form.querySelectorAll(':scope h2, :scope h3')).filter(h => h.textContent.trim().length>0);
if (headers.length >= 2) {
const acc = document.createElement('div');
acc.className = 'accordion my-3'; acc.id = 'autoAccordion-' + Math.random().toString(36).slice(2);
let startIdx = 0;
headers.forEach((h, i) => {
const itemId = acc.id + '-item-' + i;
const headerId = itemId + '-hdr';
const bodyId = itemId + '-body';
const item = document.createElement('div'); item.className = 'accordion-item bg-body';
const btnText = h.textContent.trim();
// gather content until next header or end
const chunk = [];
let el = h.nextElementSibling;
while (el && !headers.includes(el)) {
chunk.push(el);
el = el.nextElementSibling;
}
h.remove();
const bodyInner = document.createElement('div');
chunk.forEach(c => bodyInner.appendChild(c));
item.innerHTML = `
<h2 class="accordion-header" id="${headerId}">
<button class="accordion-button ${i>0 ? 'collapsed' : ''}" type="button" data-bs-toggle="collapse" data-bs-target="#${bodyId}" aria-expanded="${i===0?'true':'false'}" aria-controls="${bodyId}">
${btnText}
</button>
</h2>
<div id="${bodyId}" class="accordion-collapse collapse ${i===0?'show':''}" aria-labelledby="${headerId}" data-bs-parent="#${acc.id}">
<div class="accordion-body"></div>
</div>`;
item.querySelector('.accordion-body').appendChild(bodyInner);
acc.appendChild(item);
});
// Insert at top of form
form.prepend(acc);
}
});
// Focus first control
const first = document.querySelector('form input, form select, form textarea');
if (first) first.focus({preventScroll:true});
})();

BIN
templates.tar.gz Normal file

Binary file not shown.

View File

@@ -0,0 +1,45 @@
{% set active_page = active_page|default('') %}
<!doctype html>
<html lang="pl" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}HAProxy Configurator{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% block head %}{% endblock %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body class="bg-body">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark border-bottom border-secondary sticky-top">
<div class="container-fluid">
<a class="navbar-brand fw-semibold" href="{{ url_for('main.index') }}">HAProxy Configurator</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample" aria-controls="navbarsExample" aria-expanded="false" aria-label="Przełącz nawigację">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExample">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link {% if active_page=='home' %}active{% endif %}" href="{{ url_for('main.home') }}">Pulpit</a></li>
<li class="nav-item"><a class="nav-link {% if active_page=='index' %}active{% endif %}" href="{{ url_for('main.index') }}">Konfiguracja</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('display_haproxy_stats') }}">Statystyki</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('display_logs') }}">Logi</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('edit.edit_haproxy_config') }}">Edytor</a></li>
</ul>
</div>
</div>
</nav>
<main class="container py-4">
{% with messages = get_flashed_messages() %}{% if messages %}<div id="_flash_msgs" data-msgs="{{ messages|tojson }}"></div>{% endif %}{% endwith %}
{% block breadcrumb %}{% endblock %}
<div id="toast-stack" class="toast-container position-fixed top-0 end-0 p-3"></div>
{% block content %}{% endblock %}
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,90 @@
{% extends "base.html" %}
{% set active_page = "" %}
{% block title %}HAProxy • Edit{% endblock %}
{% block head %}
{% endblock %}
{% block breadcrumb %}<nav aria-label="breadcrumb" class="mb-3"><ol class="breadcrumb mb-0"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item active" aria-current="page">Edytor</li></ol></nav>{% endblock %}
{% block content %}
<header class="header1" id="header1">
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/" class="menu-link">Add Frontend & Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8404/stats" class="menu-link" target="_blank">HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</header>
<div style=" border-radius: 5px;" id="editor_container" class="container mt-5">
<h3 style="color: grey; padding: 15px;" id="edit_conf" class="edit_conf">Edit HAProxy Config</h3>
<form method="POST">
<div class="form-group">
<label for="haproxy_config">Configuration:</label>
<textarea style="padding: 15px;" class="form-control" name="haproxy_config" rows="25" cols="120">{{ config_content }}</textarea>
</div>
<div style="padding-bottom: 20px;" class="form-group">
<input type="submit" class="btn btn-warning" id="save_check" name="save_check" value="Save & Check">
<input type="submit" class="btn btn-primary" name="save_reload" value="Save & Restart">
</div>
</form>
{% if check_output %}
<div style="padding-bottom: 15px;">
{% if 'Fatal errors' in check_output %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% elif 'Warnings' in check_output %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% elif 'error detected while parsing an' in check_output %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% else %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% endif %}
</div>
{% endif %}
<!-- Add Bootstrap JS and jQuery scripts here (if needed) -->
<!-- You can get them from the official Bootstrap website or use CDN links -->
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -0,0 +1,69 @@
{% extends "base.html" %}
{% set active_page = "home" %}
{% block title %}HAProxy • Home{% endblock %}
{% block head %}
{% endblock %}
{% block breadcrumb %}<nav aria-label="breadcrumb" class="mb-3"><ol class="breadcrumb mb-0"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item active" aria-current="page">Pulpit</li></ol></nav>{% endblock %}
{% block content %}
<header class="header1" id="header1">
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/" class="menu-link">Add Frontend & Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8404/stats" class="menu-link" >HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
</header>
<div style=" border-radius: 5px; padding: 40px;" id="summary_container" class="container mt-5">
<h3 style="margin-bottom: 20px;" class="mt-4">Welcome to Your HAProxy Configurator. Here's A Short Summary:</h3>
<p class="lead"><i style="margin: 8px;" class="fas fa-globe"></i> <strong>{{ frontend_count }}</strong> frontends</p>
<p class="lead"><i style="margin-right: 8px;" class="fas fa-sitemap"></i> <strong>{{ backend_count }}</strong> backends</p>
<p class="lead"><i style="margin: 8px;" class="fas fa-user-lock"></i> <strong>{{ acl_count }}</strong> acl's</p>
<p class="lead"><i style="margin: 8px;" class="fas fa-code"></i> <strong>{{ layer7_count }}</strong> layer7(mode http) loadbalanced frontends</p>
<p class="lead"><i style="margin: 8px;" class="fas fa-network-wired"></i> <strong>{{ layer4_count }}</strong> layer4(mode tcp)loadbalanced frontends</p>
<div class="mt-4">
<a href="/" class="btn btn-primary"><i style="margin: 8px;" class="fas fa-plus"></i>Add New Frontend/Backend</a>
</div>
</div>
<!-- Add Bootstrap JS and jQuery links here (if needed) -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -0,0 +1,738 @@
{% extends "base.html" %}
{% set active_page = "index" %}
{% block title %}HAProxy • Index{% endblock %}
{% block head %}
{% endblock %}
{% block breadcrumb %}<nav aria-label="breadcrumb" class="mb-3"><ol class="breadcrumb mb-0"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item active" aria-current="page">Konfiguracja</li></ol></nav>{% endblock %}
{% block content %}
<!-- Header with the Edit link as a menu -->
<header id="header1" class="header1">
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link" >Home</a>
<a href="/" class="menu-link">Add Frontend&Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8404/stats" class="menu-link" >HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</header>
<div class="container mt-4">
<div style=" border-radius: 5px; padding: 20px;" class="container mt-5" id="frontend_container">
<label style="margin: 20px auto; font-size: 18px; font-weight: bold;"><i style="margin: 8px;" class="fas fa-globe"></i> Add New Frontend:</label><br>
<!-- Display success message when the form is submitted successfully -->
{% if message %}
<div class="alert {% if 'already exists' in message %}alert-danger{% else %}alert-success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
<form method="post" action="/">
<div class="form-row">
<div class="form-group col-md-4">
<label for="frontend_name">Frontend Name:</label>
<input type="text" class="form-control" name="frontend_name" required>
</div>
<div class="form-group col-md-4">
<label for="frontend_ip">Frontend IP:</label>
<input type="text" class="form-control" name="frontend_ip" required>
</div>
<div class="form-group col-md-3">
<label for="frontend_port">Frontend Port:</label>
<input type="number" class="form-control" name="frontend_port" required>
</div>
</div>
<!-- Checkbox for SSL Certificate -->
<div class="form-check">
<input type="checkbox" class="form-check-input" id="ssl_checkbox" name="ssl_checkbox">
<label class="form-check-label" for="ssl_checkbox"><i style="margin: 8px;" class="fas fa-lock"></i>Use SSL Certificate</label>
</div>
<!-- Fields for SSL Certificate Path and SSL Key Path (Hidden by default) -->
<div class="form-group" style="display: none;" id="ssl_fields">
<label for="ssl_cert_path">SSL Certificate Path:</label>
<input type="text" id="ssl_cert_path" class="form-control" name="ssl_cert_path">
<div style="padding-bottom: 15px;" class="form-check">
<input type="checkbox" class="form-check-input" id="ssl_redirect_checkbox" name="ssl_redirect_checkbox">
<label class="form-check-label" for="ssl_redirect_checkbox"><i style="margin: 8px;" class="fas fa-arrow-circle-right"></i>Add Redirect to HTTPS</label>
</div>
</div>
<div style="margin-top: 15px;" class="form-group">
<label for="lb_method">Load Balancing Method:</label>
<select class="form-control" name="lb_method" id="lb_method">
<option value="roundrobin">Round Robin</option>
<option value="leastconn">Least Connections</option>
<option value="source">Source</option>
<option value="wrr">WRR</option>
<option value="wlc">WLC</option>
<option value="random">Random</option>
</select>
</div>
<div class="form-group">
<label for="protocol">Protocol:</label>
<select class="form-control" name="protocol" id="protocol" required>
<option value="" disabled selected>--Select mode--</option>
<option value="tcp">TCP</option>
<option value="http">HTTP</option>
</select>
</div>
<!-- DOS Protection -->
<div class="form-check">
<input type="checkbox" class="form-check-input" name="add_dos" id="add_dos">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_dos"><i style="margin: 8px;" class="fas fa-shield-alt"></i> Add DOS Protection</label>
</div>
<div class="form-group" id="dos_fields" style="display: none; padding-bottom: 20px;">
<label for="limit_requests">Limit Requests, e.g: 20:</label>
<input type="text" class="form-control" name="limit_requests">
<label for="ban_duration">IP Ban Duration(in seconds, e.g: 15s):</label>
<input type="text" class="form-control" name="ban_duration">
</div>
<!-- SQL Injection -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="sql_injection_container">
<input type="checkbox" class="form-check-input" id="sql_injection_check" name="sql_injection_check">
<label class="form-check-label" for="sql_injection_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate SQL Injection Protection</label>
</div>
<!-- XSS -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="XSS_container">
<input type="checkbox" class="form-check-input" id="xss_check" name="xss_check">
<label class="form-check-label" for="xss_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate XSS Protection</label>
</div>
<!-- Remote File Uploads -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="remote_uploads_container">
<input type="checkbox" class="form-check-input" id="remote_uploads_check" name="remote_uploads_check">
<label class="form-check-label" for="remote_uploads_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate Remote File Upload Protection</label>
</div>
<!-- Deny Webshells -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="webshells_container">
<input type="checkbox" class="form-check-input" id="webshells_check" name="webshells_check">
<label class="form-check-label" for="webshells_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate Webshells Protection</label>
</div>
<div class="form-check" style="margin-top: 8px;">
<input type="checkbox" class="form-check-input" name="add_acl" id="add_acl">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_acl"><i style="margin: 8px;" class="fas fa-user-lock"></i> Add ACL for Frontend</label>
</div>
<div class="form-group" id="acl_fields" style="display: none; padding-bottom: 20px;">
<label for="acl">ACL:</label>
<input type="text" class="form-control" name="acl" placeholder="example: acl_name">
<label for="acl_action">ACL Action:</label>
<input type="text" class="form-control" name="acl_action" placeholder="example: hdr(host) -i test.com">
<label for="backend_name_acl">Backend Name:</label>
<input type="text" class="form-control" name="backend_name_acl" placeholder="example: somebackend">
</div>
<div class="form-check" id="forbidden_acl_container" style="display: none; margin-top: 8px;">
<input type="checkbox" class="form-check-input" name="add_acl_path" id="add_acl_path">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_acl_path"><i style="margin: 8px;" class="fas fa-ban"></i> Block Sensitive Path</label>
</div>
<div class="form-group" id="forbidden_fields" style="display: none; padding-bottom: 20px;">
<label for="forbidden_name">ACL Name:</label>
<input type="text" class="form-control" name="forbidden_name" id="forbidden_name">
<label for="allowed_ip">Allowed IP Addresses(e.g: 192.168.3.13 193.168.3.14):</label>
<input type="text" class="form-control" name="allowed_ip">
<label for="forbidden_path">Path(e.g: /admin):</label>
<input type="text" class="form-control" name="forbidden_path">
</div>
<div class="form-check" id="path_based_container" style="display: none; margin-top: 8px;">
<input type="checkbox" class="form-check-input" name="add_path_based" id="add_path_based">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_path_based"><i style="margin: 8px;" class="fas fa-arrow-circle-right"></i>Add Path Based Redirect</label>
</div>
<div class="form-group" id="base_redirect_fields" style="display: none; padding-bottom: 20px;">
<label for="redirect_domain_name">Domain to be redirected:</label>
<input type="text" class="form-control" name="redirect_domain_name" id="redirect_domain_name" placeholder="e.g: test2.com or test2.com:8888 (port matters incase of unusual ports)">
<label for="root_redirect">Root Path To Be Redirected:</label>
<input type="text" class="form-control" name="root_redirect" placeholder="e.g: /">
<label for="redirect_to">Redirect To Path:</label>
<input type="text" class="form-control" name="redirect_to" placeholder="e.g: /test **this will redirect test2.com/ to test2.com/test**">
</div>
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="forward_for_container">
<input type="checkbox" class="form-check-input" id="forward_for_check" name="forward_for_check">
<label class="form-check-label" for="forward_for_check"><i style="margin: 8px;" class="fas fa-network-wired"></i>Use option forwardfor</label>
</div>
<div style="border-radius: 5px; padding: 20px; margin-bottom: 20px;" class="container mt-5" id="backend_container">
<div class="form-group">
<label style="margin-top: 40px; margin-bottom: 30px; font-size: 18px; font-weight: bold;"><i style="margin-right: 8px;" class="fas fa-sitemap"></i> Backend Servers Pool:</label><br>
<!-- HTTP Health Check -->
<div class="form-group" id="health_check_container_http" style="display: none;">
<input type="checkbox" name="health_check" id="health_check" onchange="toggleHealthCheck()">
<label for="health_check">
<i style="margin: 8px;" class="fas fa-heartbeat"></i>
Enable Health Check <strong>(HTTP mode only)</strong>
</label>
</div>
<div class="form-group" id="health_check_field" style="display: none;">
<label for="health_check_link">Health Check URL (e.g: your-website.com/health.html):</label>
<input type="text" class="form-control" name="health_check_link">
</div>
<!-- TCP Health Check -->
<div class="form-group" id="health_check_container_tcp" style="display: none;">
<input type="checkbox" name="health_check2" id="health_check2" onchange="toggleHealthCheck2()">
<label for="health_check2">
<i style="margin: 8px;" class="fas fa-heartbeat"></i>
Enable Health Check <strong>(TCP mode)</strong>
</label>
</div>
<!-- Sticky Session -->
<div class="form-group">
<input type="checkbox" name="sticky_session" id="sticky_session" onclick="toggleStickySession()">
<label for="sticky_session"><i style="margin: 8px;" class="fas fa-link"></i> Enable Sticky Session</label>
</div>
<div class="form-group" id="sticky_session_field" style="display: none;">
<label for="sticky_session_type">Session Persistence Type:</label>
<select class="form-control" name="sticky_session_type">
<option value="cookie">Cookie SERVERID</option>
<option value="stick-table">Stick-Table ip</option>
</select>
</div>
<!-- Custom Header -->
<div class="form-group">
<input type="checkbox" name="add_header" id="add_header" onclick="toggleCustomHeader()">
<label for="add_header"><i style="margin: 8px;" class="fas fa-reply"></i> Add Custom Header</label>
</div>
<div class="form-group" id="header_field" style="display: none;">
<label for="header_name">Header Name:</label>
<input type="text" class="form-control" name="header_name" placeholder="e.g: X-Client-IP">
<label for="header_value">Header Value:</label>
<input type="text" class="form-control" name="header_value" placeholder="e.g: test">
</div>
<div class="form-group">
<label for="backend_name">Backend Name:</label>
<input type="text" class="form-control" name="backend_name" required>
</div>
<!-- Backend Servers Section -->
<div id="backend_servers_container">
<!-- Initial Backend Server Row -->
<div class="form-row backend-server-row">
<div class="form-group col-md-3">
<label for="name1">Backend Server Name:</label>
<input type="text" id="name1" class="form-control" name="backend_server_names[]" placeholder="server1" required>
</div>
<div class="form-group col-md-3">
<label for="ip1">Backend Server IP:</label>
<input type="text" id="ip1" class="form-control" name="backend_server_ips[]" required>
</div>
<div class="form-group col-md-3">
<label for="port1">Backend Server Port:</label>
<input type="number" id="port1" class="form-control" name="backend_server_ports[]" required>
</div>
<div class="form-group col-md-3">
<label for="maxconn1">MaxConn:</label>
<input type="number" id="maxconn1" class="form-control" name="backend_server_maxconns[]">
</div>
</div>
</div>
<button type="button" class="btn btn-secondary" onclick="addBackendServer()">+ Add Backend Server</button>
<input type="submit" id="success_btn" class="btn btn-success" value="Submit">
</div>
</div>
<script>
// Function to add a new backend server row
let serverCount = 1;
function addBackendServer() {
serverCount++;
const container = document.getElementById('backend_servers_container');
const row = document.createElement('div');
row.classList.add('form-row', 'backend-server-row', 'mt-2');
// Generate unique IDs
const nameId = `name${serverCount}`;
const ipId = `ip${serverCount}`;
const portId = `port${serverCount}`;
const maxconnId = `maxconn${serverCount}`;
row.innerHTML = `
<div class="form-group col-md-3">
<label for="${nameId}">Backend Server Name:</label>
<input type="text" id="${nameId}" class="form-control" name="backend_server_names[]" placeholder="server${serverCount}" required>
</div>
<div class="form-group col-md-3">
<label for="${ipId}">Backend Server IP:</label>
<input type="text" id="${ipId}" class="form-control" name="backend_server_ips[]" required>
</div>
<div class="form-group col-md-3">
<label for="${portId}">Backend Server Port:</label>
<input type="number" id="${portId}" class="form-control" name="backend_server_ports[]" required>
</div>
<div class="form-group col-md-3">
<label for="${maxconnId}">MaxConn:</label>
<input type="number" id="${maxconnId}" class="form-control" name="backend_server_maxconns[]">
<button type="button" class="btn btn-danger btn-sm mt-2" onclick="removeBackendServer(this)">Remove</button>
</div>
`;
container.appendChild(row);
}
function removeBackendServer(button) {
const row = button.closest('.backend-server-row');
if (document.querySelectorAll('.backend-server-row').length > 1) {
row.remove();
} else {
alert("You must have at least one backend server.");
}
}
// Protocol change handler to show/hide health check options
document.getElementById('protocol').addEventListener('change', function() {
const protocol = this.value;
const httpHealthCheck = document.getElementById('health_check_container_http');
const tcpHealthCheck = document.getElementById('health_check_container_tcp');
if (protocol === 'http') {
httpHealthCheck.style.display = 'block';
tcpHealthCheck.style.display = 'none';
} else if (protocol === 'tcp') {
httpHealthCheck.style.display = 'none';
tcpHealthCheck.style.display = 'block';
} else {
httpHealthCheck.style.display = 'none';
tcpHealthCheck.style.display = 'none';
}
});
// Toggle functions (you'll need to implement these based on your existing JavaScript)
function toggleHealthCheck() {
const field = document.getElementById('health_check_field');
const checkbox = document.getElementById('health_check');
field.style.display = checkbox.checked ? 'block' : 'none';
}
function toggleHealthCheck2() {
// Implement TCP health check toggle if needed
}
function toggleStickySession() {
const field = document.getElementById('sticky_session_field');
const checkbox = document.getElementById('sticky_session');
field.style.display = checkbox.checked ? 'block' : 'none';
}
function toggleCustomHeader() {
const field = document.getElementById('header_field');
const checkbox = document.getElementById('add_header');
field.style.display = checkbox.checked ? 'block' : 'none';
}
</script>
</form>
<!-- Add Bootstrap JS and jQuery (required for some Bootstrap components) -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
<script>
// Function to hide the success message after a few seconds
$(document).ready(function() {
setTimeout(function() {
$('.alert').alert('close');
}, 5000); // Hide the message after 3 seconds (adjust the time as needed)
});
</script>
<script>
// Function to toggle the sticky session field based on checkbox selection
function toggleCustomHeader() {
const CustomHeaderbox = document.getElementById('add_header');
const CustomHeaderField = document.getElementById('header_field');
if (CustomHeaderbox.checked) {
CustomHeaderField.style.display = 'block';
} else {
CustomHeaderField.style.display = 'none';
}
}
// Event listener to toggle the sticky session field when checkbox is clicked
document.getElementById('add_header').addEventListener('click', toggleCustomHeader);
</script>
<script>
// Function to toggle the health check field based on the protocol selected
function toggleHealthCheck() {
const healthCheckField = document.getElementById('health_check_field');
const healthCheckCheckbox = document.getElementById('health_check');
if (healthCheckCheckbox.checked) {
healthCheckField.style.display = 'block';
} else {
healthCheckField.style.display = 'none';
}
}
// Event listener to toggle the health check field when protocol changes
document.querySelector('select[name="protocol"]').addEventListener('change', toggleHealthCheck);
</script>
<script>
function toggleHealthCheck2() {
const protocolSelect = document.getElementById('protocol');
const healthCheckContainer = document.getElementById('health_check_container');
const healthCheckContainer2 = document.getElementById('health_check_container2');
const healthCheckCheckbox2 = document.getElementById('health_check2');
if (protocolSelect.value !== 'http') {
healthCheckContainer.style.display = 'none';
healthCheckContainer2.style.display = 'block';
}
else {
healthCheckContainer.style.display = 'block';
}
}
document.getElementById('protocol').addEventListener('change', toggleHealthCheck2);
</script>
<script>
function toggleHealthCheck() {
const protocolSelect = document.getElementById('protocol');
const healthCheckContainer = document.getElementById('health_check_container');
const healthCheckField = document.getElementById('health_check_field');
const healthCheckCheckbox = document.getElementById('health_check');
const healthCheckContainer2 = document.getElementById('health_check_container2');
const healthCheckCheckbox2 = document.getElementById('health_check2');
if (protocolSelect.value === 'http') {
healthCheckContainer.style.display = 'block';
if (healthCheckCheckbox.checked) {
healthCheckField.style.display = 'block';
} else {
healthCheckField.style.display = 'none';
healthCheckContainer2.style.display = 'none';
healthCheckCheckbox2.style.display = 'none';
}
} else {
healthCheckContainer.style.display = 'none';
healthCheckField.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleHealthCheck);
</script>
<script>
// Function to toggle the sticky session field based on checkbox selection
function toggleStickySession() {
const stickySessionCheckbox = document.getElementById('sticky_session');
const stickySessionField = document.getElementById('sticky_session_field');
if (stickySessionCheckbox.checked) {
stickySessionField.style.display = 'block';
} else {
stickySessionField.style.display = 'none';
}
}
// Event listener to toggle the sticky session field when checkbox is clicked
document.getElementById('sticky_session').addEventListener('click', toggleStickySession);
</script>
<script>
// Function to toggle the visibility of ACL fields based on the checkbox
const DosCheckbox = document.getElementById('add_dos');
const DosFields = document.getElementById('dos_fields');
DosCheckbox.addEventListener('change', () => {
if (DosCheckbox.checked) {
DosFields.style.display = 'block';
} else {
DosFields.style.display = 'none';
}
});
</script>
<script>
// Function to toggle the visibility of ACL fields based on the checkbox
const aclCheckbox = document.getElementById('add_acl');
const aclFields = document.getElementById('acl_fields');
aclCheckbox.addEventListener('change', () => {
if (aclCheckbox.checked) {
aclFields.style.display = 'block';
} else {
aclFields.style.display = 'none';
}
});
</script>
<script>
function toggleSqlInjection() {
const protocolSelect5 = document.getElementById('protocol');
const SqlInjectionContainer = document.getElementById('sql_injection_container');
if (protocolSelect5.value === 'http') {
SqlInjectionContainer.style.display = 'block';
}
else {
SqlInjectionContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleSqlInjection);
</script>
<script>
function Webshells() {
const protocolSelect8 = document.getElementById('protocol');
const WebshellsContainer = document.getElementById('webshells_container');
if (protocolSelect8.value === 'http') {
WebshellsContainer.style.display = 'block';
}
else {
WebshellsContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', Webshells);
</script>
<script>
function RemoteUploads() {
const protocolSelect7 = document.getElementById('protocol');
const RemoteUploadsContainer = document.getElementById('remote_uploads_container');
if (protocolSelect7.value === 'http') {
RemoteUploadsContainer.style.display = 'block';
}
else {
RemoteUploadsContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', RemoteUploads);
</script>
<script>
function toggleXSS() {
const protocolSelect6 = document.getElementById('protocol');
const XSSContainer = document.getElementById('XSS_container');
if (protocolSelect6.value === 'http') {
XSSContainer.style.display = 'block';
}
else {
XSSContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleXSS);
</script>
<script>
function toggleForwardFor() {
const protocolSelect4 = document.getElementById('protocol');
const ForwardForContainer = document.getElementById('forward_for_container');
if (protocolSelect4.value === 'http') {
ForwardForContainer.style.display = 'block';
}
else {
ForwardForContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleForwardFor);
</script>
<script>
function toggleForbiddenCheck() {
const protocolSelect3 = document.getElementById('protocol');
const forbiddenContainer = document.getElementById('forbidden_acl_container');
if (protocolSelect3.value === 'http') {
forbiddenContainer.style.display = 'block';
}
else {
forbiddenContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleForbiddenCheck);
</script>
<script>
function PathBasedToggle() {
const protocolSelect7 = document.getElementById('protocol');
const path_based_container = document.getElementById('path_based_container');
if (protocolSelect7.value === 'http') {
path_based_container.style.display = 'block';
}
else {
path_based_container.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', PathBasedToggle);
</script>
<script>
// Function to toggle the visibility of based path redirect fields based on the checkbox
const add_path_based_checkbox = document.getElementById('add_path_based');
const redirect_path_fields = document.getElementById('base_redirect_fields');
add_path_based_checkbox.addEventListener('change', () => {
if (add_path_based_checkbox.checked) {
redirect_path_fields.style.display = 'block';
} else {
redirect_path_fields.style.display = 'none';
}
});
</script>
<script>
// Function to toggle the visibility of ACL forbidden paths fields based on the checkbox
const aclCheckbox2 = document.getElementById('add_acl_path');
const aclFields2 = document.getElementById('forbidden_fields');
aclCheckbox2.addEventListener('change', () => {
if (aclCheckbox2.checked) {
aclFields2.style.display = 'block';
} else {
aclFields2.style.display = 'none';
}
});
</script>
<script>
document.getElementById('ssl_checkbox').addEventListener('change', function () {
const sslFields = document.getElementById('ssl_fields');
sslFields.style.display = this.checked ? 'block' : 'none';
});
</script>
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -0,0 +1,104 @@
{% extends "base.html" %}
{% set active_page = "" %}
{% block title %}HAProxy • Logs{% endblock %}
{% block head %}
{% endblock %}
{% block breadcrumb %}<nav aria-label="breadcrumb" class="mb-3"><ol class="breadcrumb mb-0"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item active" aria-current="page">Logi</li></ol></nav>{% endblock %}
{% block content %}
<header class="header1" id="header1>
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/" class="menu-link">Add Frontend&Backend</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8484/stats" class="menu-link" >HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</header>
<h3 style="margin-top: 30px; margin-bottom: 30px; margin-left: 10%;" id="status_header">Status 403 Forbidden Log Entries</h3>
<div>
{% for entry in entries %}
<div class="log-entry" style="padding: 20px; width: 80%; margin-left: 10%; margin-bottom: 1%;">
<p><strong>Time Stamp:</strong> {{ entry['timestamp'] }}</p>
<p><strong>IP Address:</strong> {{ entry['ip_address'] }}</p>
<p><strong>HTTP Method:</strong> {{ entry['http_method'] }}</p>
<p id="requested_url"><strong>Requested URL:</strong> {{ entry['requested_url'] }}</p>
<!-- XSS Category -->
{% if entry['xss_alert'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#xssCollapse{{ loop.index }}">XSS Alert <span class="text-danger">(Click to show details)</span></p>
<div id="xssCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['xss_alert'] }}</strong></p>
</div>
{% endif %}
<!-- SQL Category -->
{% if entry['sql_alert'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#sqlCollapse{{ loop.index }}">SQL Alert <span class="text-danger">(Click to show details)</span></p>
<div id="sqlCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['sql_alert'] }}</strong></p>
</div>
{% endif %}
<!-- PUT Method Category -->
{% if entry['put_method'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#putMethodCollapse{{ loop.index }}">PUT Method Alert <span class="text-danger">(Click to show details)</span></p>
<div id="putMethodCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['put_method'] }}</strong></p>
</div>
{% endif %}
<!-- Illegal Resource Access -->
{% if entry['illegal_resource'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#putMethodCollapse{{ loop.index }}">Illegal Resource Access Alert <span class="text-danger">(Click to show details)</span></p>
<div id="putMethodCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['illegal_resource'] }}</strong></p>
</div>
{% endif %}
<!-- Illegal Resource Access -->
{% if entry['webshell_alert'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#putMethodCollapse{{ loop.index }}">WebShell Attack Alert <span class="text-danger">(Click to show details)</span></p>
<div id="putMethodCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['webshell_alert'] }}</strong></p>
</div>
{% endif %}
<p><strong>Status Code:</strong> 403</p>
</div>
{% endfor %}
</div>
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -0,0 +1,126 @@
{% extends "base.html" %}
{% set active_page = "" %}
{% block title %}HAProxy • Statistics{% endblock %}
{% block head %}
{% endblock %}
{% block breadcrumb %}<nav aria-label="breadcrumb" class="mb-3"><ol class="breadcrumb mb-0"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item active" aria-current="page">Statystyki</li></ol></nav>{% endblock %}
{% block content %}
<!-- Header -->
<header>
<a href="/home" style="text-decoration: none;">
<h3 style="color: grey; font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/" class="menu-link">Add Frontend&Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link active">Statistics</a>
<a href="http://{{ request.host.split(':')[0] }}:8080/stats" class="menu-link" target="_blank">HAProxy Stats</a>
</header>
<!-- Main Content -->
<div class="container">
<h1>
<i class="fas fa-chart-line"></i> HAProxy Statistics
</h1>
{% if stats %}
<!-- Summary Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-white bg-info summary-card">
<div class="card-body">
<h5 class="card-title">Total Frontends</h5>
<h2>{{ stats|length }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-success summary-card">
<div class="card-body">
<h5 class="card-title">Total Connections</h5>
<h2>{{ stats|map(attribute='conn_tot')|sum }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-warning summary-card">
<div class="card-body">
<h5 class="card-title">4xx Errors</h5>
<h2>{{ stats|map(attribute='4xx_errors')|sum }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-danger summary-card">
<div class="card-body">
<h5 class="card-title">5xx Errors</h5>
<h2>{{ stats|map(attribute='5xx_errors')|sum }}</h2>
</div>
</div>
</div>
</div>
<!-- Statistics Table -->
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th><i class="fas fa-code"></i> Frontend Name</th>
<th><i class="fas fa-server"></i> Server Name</th>
<th class="text-center">4xx Errors</th>
<th class="text-center">5xx Errors</th>
<th class="text-right">Bytes In (MB)</th>
<th class="text-right">Bytes Out (MB)</th>
<th class="text-center">Total Connections</th>
</tr>
</thead>
<tbody>
{% for stat in stats %}
<tr>
<td>
<span class="badge bg-info">{{ stat.frontend_name }}</span>
</td>
<td>
<span class="badge bg-secondary">{{ stat.server_name }}</span>
</td>
<td class="text-center">
{% if stat['4xx_errors'] > 0 %}
<span class="badge bg-warning">{{ stat['4xx_errors'] }}</span>
{% else %}
<span class="badge bg-success">{{ stat['4xx_errors'] }}</span>
{% endif %}
</td>
<td class="text-center">
{% if stat['5xx_errors'] > 0 %}
<span class="badge bg-danger">{{ stat['5xx_errors'] }}</span>
{% else %}
<span class="badge bg-success">{{ stat['5xx_errors'] }}</span>
{% endif %}
</td>
<td class="text-right">{{ "%.2f"|format(stat.bytes_in_mb) }}</td>
<td class="text-right">{{ "%.2f"|format(stat.bytes_out_mb) }}</td>
<td class="text-center">
<strong>{{ stat.conn_tot }}</strong>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info no-data">
<i class="fas fa-info-circle"></i> No statistics available
</div>
{% endif %}
</div>
<!-- Footer -->
<footer>
<p>&copy; 2025 HAProxy Configurator. All rights reserved.</p>
</footer>
{% endblock %}
{% block scripts %}
{% endblock %}