diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 0000000..f4d0219 --- /dev/null +++ b/static/css/main.css @@ -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} + \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..3f0d21d --- /dev/null +++ b/static/js/main.js @@ -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 = `
+
${m}
+ +
`; + 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 = ` 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 = ` +

+ +

+
+
+
`; + 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}); + +})(); diff --git a/templates.tar.gz b/templates.tar.gz new file mode 100644 index 0000000..5e73581 Binary files /dev/null and b/templates.tar.gz differ diff --git a/templates/templates/base.html b/templates/templates/base.html new file mode 100644 index 0000000..fa99cab --- /dev/null +++ b/templates/templates/base.html @@ -0,0 +1,45 @@ +{% set active_page = active_page|default('') %} + + + + + + {% block title %}HAProxy Configurator{% endblock %} + + + {% block head %}{% endblock %} + + + + + +
+ {% with messages = get_flashed_messages() %}{% if messages %}
{% endif %}{% endwith %} + + {% block breadcrumb %}{% endblock %} +
+ {% block content %}{% endblock %} + +
+ + + + {% block scripts %}{% endblock %} + + diff --git a/templates/templates/edit.html b/templates/templates/edit.html new file mode 100644 index 0000000..bd5a4d5 --- /dev/null +++ b/templates/templates/edit.html @@ -0,0 +1,90 @@ +{% extends "base.html" %} +{% set active_page = "" %} +{% block title %}HAProxy • Edit{% endblock %} +{% block head %} +{% endblock %} +{% block breadcrumb %}{% endblock %} +{% block content %} +
+ + + + Home + Add Frontend & Backend + Edit HAProxy Config + Security Events + Statictics + HAProxy Stats + +
+ + +
+ + + +
+
+

Edit HAProxy Config

+
+
+ + +
+
+ + +
+
+ {% if check_output %} +
+ {% if 'Fatal errors' in check_output %} + + {% elif 'Warnings' in check_output %} + + {% elif 'error detected while parsing an' in check_output %} + + {% else %} + + {% endif %} +
+ {% endif %} + + + + + + +{% endblock %} +{% block scripts %} +{% endblock %} \ No newline at end of file diff --git a/templates/templates/home.html b/templates/templates/home.html new file mode 100644 index 0000000..87bc668 --- /dev/null +++ b/templates/templates/home.html @@ -0,0 +1,69 @@ +{% extends "base.html" %} +{% set active_page = "home" %} +{% block title %}HAProxy • Home{% endblock %} +{% block head %} +{% endblock %} +{% block breadcrumb %}{% endblock %} +{% block content %} +
+ + + + Home + Add Frontend & Backend + Edit HAProxy Config + Security Events + Statictics + HAProxy Stats + +
+ + +
+ +
+ +
+

Welcome to Your HAProxy Configurator. Here's A Short Summary:

+ +

{{ frontend_count }} frontends

+

{{ backend_count }} backends

+

{{ acl_count }} acl's

+

{{ layer7_count }} layer7(mode http) loadbalanced frontends

+

{{ layer4_count }} layer4(mode tcp)loadbalanced frontends

+ +
+ + + + + + + +{% endblock %} +{% block scripts %} +{% endblock %} \ No newline at end of file diff --git a/templates/templates/index.html b/templates/templates/index.html new file mode 100644 index 0000000..c240c62 --- /dev/null +++ b/templates/templates/index.html @@ -0,0 +1,738 @@ +{% extends "base.html" %} +{% set active_page = "index" %} +{% block title %}HAProxy • Index{% endblock %} +{% block head %} +{% endblock %} +{% block breadcrumb %}{% endblock %} +{% block content %} + + +
+ + + + Home + Add Frontend&Backend + Edit HAProxy Config + Security Events + Statictics + HAProxy Stats +
+ + +
+ + +
+ + +
+
+
+ + {% if message %} + + {% endif %} + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + +
+
+
+ + + + + + + + + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endblock %} +{% block scripts %} +{% endblock %} \ No newline at end of file diff --git a/templates/templates/logs.html b/templates/templates/logs.html new file mode 100644 index 0000000..123bb0e --- /dev/null +++ b/templates/templates/logs.html @@ -0,0 +1,104 @@ +{% extends "base.html" %} +{% set active_page = "" %} +{% block title %}HAProxy • Logs{% endblock %} +{% block head %} +{% endblock %} +{% block breadcrumb %}{% endblock %} +{% block content %} +
+ + + Home + Edit HAProxy Config + Add Frontend&Backend + Security Events + Statictics + HAProxy Stats +
+ + +
+ + + + +
+

Status 403 Forbidden Log Entries

+
+ {% for entry in entries %} +
+

Time Stamp: {{ entry['timestamp'] }}

+

IP Address: {{ entry['ip_address'] }}

+

HTTP Method: {{ entry['http_method'] }}

+

Requested URL: {{ entry['requested_url'] }}

+ + + {% if entry['xss_alert'] %} +

XSS Alert (Click to show details)

+
+

{{ entry['xss_alert'] }}

+
+ {% endif %} + + + {% if entry['sql_alert'] %} +

SQL Alert (Click to show details)

+
+

{{ entry['sql_alert'] }}

+
+ {% endif %} + + + {% if entry['put_method'] %} +

PUT Method Alert (Click to show details)

+
+

{{ entry['put_method'] }}

+
+ {% endif %} + + + {% if entry['illegal_resource'] %} +

Illegal Resource Access Alert (Click to show details)

+
+

{{ entry['illegal_resource'] }}

+
+ {% endif %} + + + {% if entry['webshell_alert'] %} +

WebShell Attack Alert (Click to show details)

+
+

{{ entry['webshell_alert'] }}

+
+ {% endif %} + +

Status Code: 403

+
+ {% endfor %} +
+{% endblock %} +{% block scripts %} +{% endblock %} \ No newline at end of file diff --git a/templates/templates/statistics.html b/templates/templates/statistics.html new file mode 100644 index 0000000..0a884fe --- /dev/null +++ b/templates/templates/statistics.html @@ -0,0 +1,126 @@ +{% extends "base.html" %} +{% set active_page = "" %} +{% block title %}HAProxy • Statistics{% endblock %} +{% block head %} +{% endblock %} +{% block breadcrumb %}{% endblock %} +{% block content %} + +
+ + + + Home + Add Frontend&Backend + Edit HAProxy Config + Security Events + Statistics + HAProxy Stats +
+ + +
+

+ HAProxy Statistics +

+ + {% if stats %} + +
+
+
+
+
Total Frontends
+

{{ stats|length }}

+
+
+
+
+
+
+
Total Connections
+

{{ stats|map(attribute='conn_tot')|sum }}

+
+
+
+
+
+
+
4xx Errors
+

{{ stats|map(attribute='4xx_errors')|sum }}

+
+
+
+
+
+
+
5xx Errors
+

{{ stats|map(attribute='5xx_errors')|sum }}

+
+
+
+
+ + +
+ + + + + + + + + + + + + + {% for stat in stats %} + + + + + + + + + + {% endfor %} + +
Frontend Name Server Name4xx Errors5xx ErrorsBytes In (MB)Bytes Out (MB)Total Connections
+ {{ stat.frontend_name }} + + {{ stat.server_name }} + + {% if stat['4xx_errors'] > 0 %} + {{ stat['4xx_errors'] }} + {% else %} + {{ stat['4xx_errors'] }} + {% endif %} + + {% if stat['5xx_errors'] > 0 %} + {{ stat['5xx_errors'] }} + {% else %} + {{ stat['5xx_errors'] }} + {% endif %} + {{ "%.2f"|format(stat.bytes_in_mb) }}{{ "%.2f"|format(stat.bytes_out_mb) }} + {{ stat.conn_tot }} +
+
+ {% else %} +
+ No statistics available +
+ {% endif %} +
+ + + +{% endblock %} +{% block scripts %} +{% endblock %} \ No newline at end of file diff --git a/templates/edit.html b/templates>/edit.html similarity index 100% rename from templates/edit.html rename to templates>/edit.html diff --git a/templates/home.html b/templates>/home.html similarity index 100% rename from templates/home.html rename to templates>/home.html diff --git a/templates/index.html b/templates>/index.html similarity index 100% rename from templates/index.html rename to templates>/index.html diff --git a/templates/logs.html b/templates>/logs.html similarity index 100% rename from templates/logs.html rename to templates>/logs.html diff --git a/templates/statistics.html b/templates>/statistics.html similarity index 100% rename from templates/statistics.html rename to templates>/statistics.html