Skip to content

Commit 7e31341

Browse files
committed
Introduce embedded HTTP server for HAProxy monitoring dashboard
- Added a Python-based HTTP server to serve `dashboard.html` at `/` or `/dashboard.html` paths. - Updated HAProxy configuration to integrate backend for the dashboard. - Enhanced handling of index paths with conditional redirects to the dashboard page. - Modified tests and updated expected outputs to reflect these changes.
1 parent 67cfcf3 commit 7e31341

16 files changed

Lines changed: 153 additions & 70 deletions

File tree

deploy/docker/assets/etc/easyhaproxy/www/dashboard.html

Lines changed: 29 additions & 29 deletions
Large diffs are not rendered by default.

docs/reference/plugins/fastcgi.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Automatically generates HAProxy `fcgi-app` configuration that defines required C
2121
| Option | Description | Default |
2222
|-------------------|-----------------------------------------|------------------------------------|
2323
| `enabled` | Enable/disable plugin | `true` |
24-
| `document_root` | Document root path | `/etc/easyhaproxy/www` |
24+
| `document_root` | Document root path | `/var/www/html` |
2525
| `script_filename` | Custom pattern for SCRIPT_FILENAME | `%[path]` (uses HAProxy's default) |
2626
| `index_file` | Default index file | `index.php` |
2727
| `path_info` | Enable PATH_INFO support | `true` |
@@ -41,10 +41,10 @@ services:
4141
easyhaproxy.http.localport: 9000
4242
easyhaproxy.http.proto: fcgi
4343
easyhaproxy.http.plugins: fastcgi
44-
easyhaproxy.http.plugin.fastcgi.document_root: /etc/easyhaproxy/www
44+
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
4545
easyhaproxy.http.plugin.fastcgi.index_file: index.php
4646
volumes:
47-
- ./app:/etc/easyhaproxy/www
47+
- ./app:/var/www/html
4848
```
4949
5050
### Docker/Docker Compose (Unix socket)
@@ -58,10 +58,10 @@ services:
5858
easyhaproxy.http.socket: /run/php/php-fpm.sock
5959
easyhaproxy.http.proto: fcgi
6060
easyhaproxy.http.plugins: fastcgi
61-
easyhaproxy.http.plugin.fastcgi.document_root: /etc/easyhaproxy/www
61+
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
6262
easyhaproxy.http.plugin.fastcgi.index_file: index.php
6363
volumes:
64-
- ./app:/etc/easyhaproxy/www
64+
- ./app:/var/www/html
6565
- /run/php:/run/php
6666
```
6767
@@ -73,7 +73,7 @@ kind: Ingress
7373
metadata:
7474
annotations:
7575
easyhaproxy.plugins: "fastcgi"
76-
easyhaproxy.plugin.fastcgi.document_root: "/etc/easyhaproxy/www"
76+
easyhaproxy.plugin.fastcgi.document_root: "/var/www/html"
7777
easyhaproxy.plugin.fastcgi.index_file: "index.php"
7878
spec:
7979
rules:
@@ -101,7 +101,7 @@ easymapping:
101101
- fastcgi
102102
plugin_config:
103103
fastcgi:
104-
document_root: /etc/easyhaproxy/www
104+
document_root: /var/www/html
105105
index_file: index.php
106106
path_info: true
107107
```
@@ -111,7 +111,7 @@ easymapping:
111111
| Environment Variable | Config Key | Type | Default | Description |
112112
|----------------------------------------------|-------------------|----------|------------------------|---------------------------------------|
113113
| `EASYHAPROXY_PLUGIN_FASTCGI_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
114-
| `EASYHAPROXY_PLUGIN_FASTCGI_DOCUMENT_ROOT` | `document_root` | string | `/etc/easyhaproxy/www` | Document root path |
114+
| `EASYHAPROXY_PLUGIN_FASTCGI_DOCUMENT_ROOT` | `document_root` | string | `/var/www/html` | Document root path |
115115
| `EASYHAPROXY_PLUGIN_FASTCGI_SCRIPT_FILENAME` | `script_filename` | string | `%[path]` | Custom pattern for SCRIPT_FILENAME |
116116
| `EASYHAPROXY_PLUGIN_FASTCGI_INDEX_FILE` | `index_file` | string | `index.php` | Default index file |
117117
| `EASYHAPROXY_PLUGIN_FASTCGI_PATH_INFO` | `path_info` | boolean | `true` | Enable PATH_INFO support |
@@ -121,7 +121,7 @@ easymapping:
121121
```haproxy
122122
# Top-level fcgi-app definition (added after defaults, before frontends/backends)
123123
fcgi-app fcgi_phpapp_local
124-
docroot /etc/easyhaproxy/www
124+
docroot /var/www/html
125125
index index.php
126126
path-info ^(/.+\.php)(/.*)?$
127127

docs/reference/volumes.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ All EasyHAProxy files are organized under `/etc/easyhaproxy/`. This can be custo
6464
6565
├── cloudflare_ips.lst # Optional - Cloudflare plugin
6666
67-
└── www/ # Optional - FastCGI document root
68-
└── index.php
67+
└── www/ # 📦 Base image - Stats dashboard
68+
└── dashboard.html # 📦 Base image - Stats dashboard UI
6969
```
7070

7171
:::tip Legend
@@ -88,7 +88,7 @@ The most commonly mapped volumes for persistence and customization:
8888
| `/etc/easyhaproxy/haproxy/errors-custom/` | [Custom error pages](other.md) - custom HTTP error pages (400, 403, 500, etc.) | Optional |
8989
| `/etc/easyhaproxy/plugins/` | [Custom plugins](../guides/plugins.md) - Python plugin files | Optional |
9090
| `/etc/easyhaproxy/jwt_keys/` | [JWT public keys](plugins/jwt-validator.md) - RSA public keys for JWT validation | Optional |
91-
| `/etc/easyhaproxy/www/` | [FastCGI document root](plugins/fastcgi.md) - PHP/FastCGI application files | Optional |
91+
| `/etc/easyhaproxy/www/` | Stats dashboard UI - served on port `stats_port + 10000` (default `11936`) | Optional |
9292

9393
## Directory Details
9494

src/easyhaproxy/main.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import os
33
import shutil
44
import sys
5+
import threading
6+
from http.server import BaseHTTPRequestHandler, HTTPServer
57

68
from deepdiff import DeepDiff
79

@@ -16,6 +18,38 @@
1618
from processor import ProcessorInterface
1719

1820

21+
class DashboardHandler(BaseHTTPRequestHandler):
22+
_content: bytes | None = None
23+
24+
def do_GET(self):
25+
if self.path in ("/", "/index.html", "/dashboard.html"):
26+
if DashboardHandler._content is None:
27+
dashboard_path = os.path.join(Consts.www_path, "dashboard.html")
28+
try:
29+
with open(dashboard_path, "rb") as f:
30+
DashboardHandler._content = f.read()
31+
except OSError:
32+
DashboardHandler._content = b""
33+
self.send_response(200)
34+
self.send_header("Content-Type", "text/html; charset=utf-8")
35+
self.send_header("Content-Length", str(len(DashboardHandler._content)))
36+
self.end_headers()
37+
self.wfile.write(DashboardHandler._content)
38+
else:
39+
self.send_response(404)
40+
self.end_headers()
41+
42+
def log_message(self, format, *args):
43+
pass
44+
45+
46+
def start_dashboard_server():
47+
server = HTTPServer(("127.0.0.1", Consts.DASHBOARD_SERVER_PORT), DashboardHandler)
48+
t = threading.Thread(target=server.serve_forever, daemon=True)
49+
t.start()
50+
logger_easyhaproxy.info(f"Dashboard server listening on 127.0.0.1:{Consts.DASHBOARD_SERVER_PORT}")
51+
52+
1953
def _build_parser() -> argparse.ArgumentParser:
2054
parser = argparse.ArgumentParser(
2155
prog="easy-haproxy",
@@ -146,6 +180,8 @@ def start():
146180
os.makedirs(Consts.certs_certbot, exist_ok=True)
147181
os.makedirs(Consts.certs_haproxy, exist_ok=True)
148182

183+
start_dashboard_server()
184+
149185
processor_obj.save_config(Consts.haproxy_config)
150186
processor_obj.save_certs(Consts.certs_haproxy)
151187
certbot_certs_found = processor_obj.get_certbot_hosts()

src/easymapping/config_generator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from jinja2 import Environment, FileSystemLoader
66

7-
from functions import Functions, logger_easyhaproxy
7+
from functions import Functions, logger_easyhaproxy, Consts
88

99
from .label_handler import DockerLabelHandler
1010

@@ -92,7 +92,8 @@ def generate(self, container_metadata={}):
9292
return template.render(
9393
data=self.mapping,
9494
global_plugin_configs=self.global_plugin_configs,
95-
defaults_plugin_configs=self.defaults_plugin_configs
95+
defaults_plugin_configs=self.defaults_plugin_configs,
96+
dashboard_server_port=Consts.DASHBOARD_SERVER_PORT
9697
)
9798

9899
def parse(self, container_metadata):

src/functions/consts.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,11 @@ def certs_certbot(cls):
5656
@classproperty
5757
def certs_haproxy(cls):
5858
"""Path to user-provided certificates directory."""
59-
return f"{cls.base_path}/certs/haproxy"
59+
return f"{cls.base_path}/certs/haproxy"
60+
61+
@classproperty
62+
def www_path(cls):
63+
"""Path to the web assets directory (dashboard, static files)."""
64+
return f"{cls.base_path}/www"
65+
66+
DASHBOARD_SERVER_PORT = 9190

src/plugins/builtin/fastcgi.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
Configuration:
1212
- enabled: Enable/disable the plugin (default: true)
13-
- document_root: Document root path (default: /etc/easyhaproxy/www)
13+
- document_root: Document root path (default: /var/www/html)
1414
- script_filename: Pattern for SCRIPT_FILENAME (default: %[path])
1515
- index_file: Default index file (default: index.php)
1616
- path_info: Enable PATH_INFO support (default: true)
@@ -39,8 +39,6 @@
3939
import os
4040
import sys
4141

42-
from functions import Consts
43-
4442
# Add parent directory to path for imports
4543
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
4644

@@ -52,7 +50,7 @@ class FastcgiPlugin(PluginInterface):
5250

5351
def __init__(self):
5452
self.enabled = True
55-
self.document_root = Consts.base_path + "/www"
53+
self.document_root = "/var/www/html"
5654
self.script_filename = "%[path]"
5755
self.index_file = "index.php"
5856
self.path_info = True

src/templates/haproxy.cfg.j2

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,14 @@ frontend dashboard
9797
mode http
9898
acl is_index path /
9999
acl is_index path /index.html
100-
http-request return status 200 content-type "text/html" file /etc/easyhaproxy/www/dashboard.html if is_index
101-
http-request return status 404
100+
acl is_dashboard path /dashboard.html
101+
http-request set-path /dashboard.html if is_index
102+
http-request return status 404 if !is_dashboard
103+
default_backend srv_dashboard
104+
105+
backend srv_dashboard
106+
mode http
107+
server Local 127.0.0.1:{{ dashboard_server_port }}
102108
{% endif %}
103109
{% for o in data["easymapping"] -%}
104110
{% set mode = o["mode"] or "http" %}

tests/conftest.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66

77
import os
88
import shutil
9-
import tempfile
109
import pytest
1110

1211

13-
# Create a session-wide temporary directory for all tests
14-
# Use a different prefix to avoid conflicts with cleanup plugin (which looks for "easyhaproxy_*")
15-
_test_session_dir = tempfile.mkdtemp(prefix="pytest_easyhaproxy_")
12+
# Fixed temporary directory for all tests — predictable so expected fixtures can reference it
13+
_test_session_dir = "/tmp/easyhaproxy_test"
14+
os.makedirs(_test_session_dir, exist_ok=True)
1615
os.environ["EASYHAPROXY_BASE_PATH"] = _test_session_dir
1716

1817

tests/expected/docker.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,14 @@ frontend dashboard
4242
mode http
4343
acl is_index path /
4444
acl is_index path /index.html
45-
http-request return status 200 content-type "text/html" file /etc/easyhaproxy/www/dashboard.html if is_index
46-
http-request return status 404
45+
acl is_dashboard path /dashboard.html
46+
http-request set-path /dashboard.html if is_index
47+
http-request return status 404 if !is_dashboard
48+
default_backend srv_dashboard
49+
50+
backend srv_dashboard
51+
mode http
52+
server Local 127.0.0.1:9190
4753

4854
frontend http_in_443
4955
bind *:443 ssl crt /etc/easyhaproxy/certs/certbot/ alpn h2,http/1.1 crt /etc/easyhaproxy/certs/haproxy/ alpn h2,http/1.1

0 commit comments

Comments
 (0)