diff --git a/http-server.py b/http-server.py
new file mode 100644
index 0000000..2580a50
--- /dev/null
+++ b/http-server.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+import http.server, socketserver, os
+
+PORT = 8080
+os.chdir(os.path.dirname(os.path.abspath(__file__)))
+
+class Handler(http.server.SimpleHTTPRequestHandler):
+ def log_message(self, fmt, *args):
+ pass
+
+socketserver.TCPServer.allow_reuse_address = True
+with socketserver.TCPServer(('', PORT), Handler) as httpd:
+ print(f'Serving on http://localhost:{PORT}')
+ httpd.serve_forever()
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..27b9555
--- /dev/null
+++ b/index.html
@@ -0,0 +1,300 @@
+
+
+
+
+
+XAMPP Control Panel
+
+
+
+
+
+
+
+
+
+
+
+
XAMPP Status
+
Unknown
+
+
+
Backend
+
Connecting...
+
+
+
+
+
All Services
+
+
+
+
+
+
+
+
+
+
+
Individual Services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xampp-backend.py b/xampp-backend.py
new file mode 100644
index 0000000..1c4d881
--- /dev/null
+++ b/xampp-backend.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+import json
+import subprocess
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+
+XAMPP = '/opt/lampp/xampp'
+ALLOWED = {'start','stop','restart','reload','status','startapache','stopapache','startmysql','stopmysql','startftp','stopftp'}
+
+class Handler(BaseHTTPRequestHandler):
+ def log_message(self, fmt, *args):
+ pass
+
+ def _cors(self):
+ self.send_header('Access-Control-Allow-Origin', '*')
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
+
+ def _send(self, code, body):
+ data = json.dumps(body).encode()
+ try:
+ self.send_response(code)
+ self.send_header('Content-Type', 'application/json')
+ self._cors()
+ self.send_header('Content-Length', str(len(data)))
+ self.end_headers()
+ self.wfile.write(data)
+ except BrokenPipeError:
+ pass
+
+ def do_OPTIONS(self):
+ self.send_response(204)
+ self._cors()
+ self.end_headers()
+
+ def do_GET(self):
+ if self.path == '/ping':
+ self._send(200, {'ok': True})
+ else:
+ self._send(404, {'error': 'Not found'})
+
+ def do_POST(self):
+ if self.path != '/run':
+ self._send(404, {'error': 'Not found'})
+ return
+ length = int(self.headers.get('Content-Length', 0))
+ body = json.loads(self.rfile.read(length))
+ cmd = body.get('cmd', '').strip()
+ if cmd not in ALLOWED:
+ self._send(400, {'error': f'Command not allowed: {cmd}'})
+ return
+ try:
+ result = subprocess.run(['sudo', XAMPP, cmd], capture_output=True, text=True, timeout=180)
+ output = (result.stdout + result.stderr).strip()
+ self._send(200, {'output': output, 'code': result.returncode})
+ except subprocess.TimeoutExpired:
+ self._send(200, {'output': 'Timeout after 180 seconds.', 'code': -1})
+ except Exception as e:
+ self._send(500, {'error': str(e)})
+
+if __name__ == '__main__':
+ server = ThreadingHTTPServer(('127.0.0.1', 5050), Handler)
+ print('XAMPP GUI Backend - Port 5050')
+ print('Open: http://localhost:8080/')
+ print('Stop: Ctrl+C')
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ pass
diff --git a/xampp-gui.sh b/xampp-gui.sh
new file mode 100644
index 0000000..30b2518
--- /dev/null
+++ b/xampp-gui.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+cd "$(dirname "$0")"
+
+pkill -f xampp-backend 2>/dev/null
+pkill -f http-server 2>/dev/null
+pkill -f 'python3 -m http.server' 2>/dev/null
+sleep 1
+
+sudo -v
+
+echo "Starting XAMPP GUI Backend..."
+python3 xampp-backend.py &
+python3 http-server.py &
+
+sleep 1
+
+xdg-open "http://localhost:8080/" 2>/dev/null || \
+ firefox "http://localhost:8080/" 2>/dev/null || \
+ echo "Open manually: http://localhost:8080/"
+
+echo "GUI running at: http://localhost:8080/"
+wait